diff --git a/java/code/src/com/redhat/rhn/common/db/datasource/xml/Package_queries.xml b/java/code/src/com/redhat/rhn/common/db/datasource/xml/Package_queries.xml
index d3a089ff4d85..1ea91a80d7e8 100644
--- a/java/code/src/com/redhat/rhn/common/db/datasource/xml/Package_queries.xml
+++ b/java/code/src/com/redhat/rhn/common/db/datasource/xml/Package_queries.xml
@@ -573,6 +573,38 @@ SELECT PN.name as NAME,
+
+
+ WITH highest_id_packages AS (
+ SELECT p.name_id, p.evr_id, p.package_arch_id, max(p.id) AS id
+ FROM rhnServerPackage sp
+ INNER JOIN rhnServer s ON sp.server_id = s.id
+ INNER JOIN rhnServerChannel sc on sc.server_id = s.id
+ INNER JOIN rhnChannelPackage cp on cp.channel_id = sc.channel_id
+ INNER JOIN rhnpackage p ON sp.evr_id = p.evr_id AND sp.name_id = p.name_id AND sp.package_arch_id = p.package_arch_id AND p.id = cp.package_id
+ WHERE sp.server_id = :sid AND (p.org_id = s.org_id or p.org_id is null)
+ GROUP BY p.name_id, p.evr_id, p.package_arch_id
+ )
+ SELECT p.id AS package_id,
+ pn.id || '|' || pe.id || '|' || pa.id as id_combo,
+ pn.name as name,
+ pa.label as arch,
+ pe.epoch as epoch,
+ pe.version as version,
+ pe.release as release,
+ pe.type AS type
+ FROM rhnPackageName pn
+ INNER JOIN rhnServerPackage sp ON sp.name_id = pn.id
+ INNER JOIN rhnServer s ON sp.server_id = s.id
+ INNER JOIN rhnPackageEvr pe ON sp.evr_id = pe.id
+ LEFT JOIN rhnPackageArch pa ON sp.package_arch_id = pa.id
+ LEFT JOIN highest_id_packages hp ON hp.name_id = sp.name_id AND hp.evr_id = sp.evr_id AND hp.package_arch_id = sp.package_arch_id
+ LEFT JOIN rhnpackage p ON hp.id = p.id
+ WHERE sp.server_id = :sid
+
+
+
+
SELECT PN.id || '|' || SPE.id AS ID_COMBO,
diff --git a/java/code/src/com/redhat/rhn/common/db/datasource/xml/file-list.xml b/java/code/src/com/redhat/rhn/common/db/datasource/xml/file-list.xml
index 5c6379e9dcfc..5038130324de 100644
--- a/java/code/src/com/redhat/rhn/common/db/datasource/xml/file-list.xml
+++ b/java/code/src/com/redhat/rhn/common/db/datasource/xml/file-list.xml
@@ -25,6 +25,7 @@
+
diff --git a/java/code/src/com/redhat/rhn/common/db/datasource/xml/oval_queries.xml b/java/code/src/com/redhat/rhn/common/db/datasource/xml/oval_queries.xml
new file mode 100755
index 000000000000..e275ccbbf381
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/common/db/datasource/xml/oval_queries.xml
@@ -0,0 +1,93 @@
+
+
+
+ call insert_product_vulnerable_packages(:package_name, :fix_version, :product_name, :cve_name)
+
+
+
+
+
+ SELECT vulnerablePkg.name AS vulnerablePkgName, vulnerablePkg.fix_version AS vulnerablePkgFixVersion
+ FROM suseovalvulnerablepackage vulnerablePkg,
+ rhncve cve,
+ suseovalplatform platform,
+ suseovalplatformvulnerablepackage platVulnerablePkg
+ WHERE cve.name = :cve_name
+ AND platform.cpe = :product_cpe
+ AND platVulnerablePkg.cve_id = cve.id
+ AND platVulnerablePkg.platform_id = platform.id
+ AND platVulnerablePkg.vulnerable_pkg_id = vulnerablePkg.id;
+
+
+
+
+
+ SELECT 1
+ FROM suseOVALPlatformVulnerablePackage platVul,
+ rhncve cve
+ WHERE platVul.cve_id = cve.id
+ AND cve.name = :cve_name;
+
+
+
+
+
+ SELECT rhnServer.id AS system_id,
+ rhnServer.name system_name,
+ rhnpackagename.name package_name,
+ rhnpackageevr.type package_type,
+ ovalVulPkg.fix_version patched_version,
+ rhnpackageevr.epoch installed_epoch,
+ rhnpackageevr.version installed_version,
+ rhnpackageevr.release installed_release,
+ rhncve.name cve
+ FROM rhnserver,
+ rhncve,
+ suseovalplatformvulnerablepackage ovalPlatVulPkg,
+ suseovalplatform ovalPlat,
+ suseovalvulnerablepackage ovalVulPkg,
+ rhnserverpackage serverPkg,
+ rhnpackagename,
+ rhnpackageevr
+ WHERE rhnserver.cpe = ovalPlat.cpe
+ AND rhnserver.id = serverPkg.server_id
+ AND rhnpackagename.id = serverPkg.name_id
+ AND rhnpackageevr.id = serverPkg.evr_id
+ AND rhnpackagename.name = ovalVulPkg.name
+ AND ovalPlatVulPkg.cve_id = rhncve.id
+ AND ovalPlatVulPkg.platform_id = ovalPlat.id
+ AND ovalPlatVulPkg.vulnerable_pkg_id = ovalVulPkg.id
+ AND rhncve.name = :cve_name;
+
+
+
+
+
+ SELECT rhnServer.id system_id,
+ rhnServer.name system_name,
+ rhnpackagename.name package_name,
+ rhnpackageevr.type package_type,
+ ovalVulPkg.fix_version patched_version,
+ rhnpackageevr.epoch installed_epoch,
+ rhnpackageevr.version installed_version,
+ rhnpackageevr.release installed_release,
+ rhncve.name cve
+ FROM rhnserver,
+ rhncve,
+ suseovalplatformvulnerablepackage ovalPlatVulPkg,
+ suseovalplatform ovalPlat,
+ suseovalvulnerablepackage ovalVulPkg,
+ rhnserverpackage serverPkg,
+ rhnpackagename,
+ rhnpackageevr
+ WHERE rhnserver.cpe = ovalPlat.cpe
+ AND rhnserver.id = serverPkg.server_id
+ AND rhnpackagename.id = serverPkg.name_id
+ AND rhnpackageevr.id = serverPkg.evr_id
+ AND rhnpackagename.name = ovalVulPkg.name
+ AND ovalPlatVulPkg.cve_id = rhncve.id
+ AND ovalPlatVulPkg.platform_id = ovalPlat.id
+ AND ovalPlatVulPkg.vulnerable_pkg_id = ovalVulPkg.id
+
+
+
diff --git a/java/code/src/com/redhat/rhn/domain/errata/Cve.java b/java/code/src/com/redhat/rhn/domain/errata/Cve.java
index 2d1b75ad6fd2..12834a7d2249 100644
--- a/java/code/src/com/redhat/rhn/domain/errata/Cve.java
+++ b/java/code/src/com/redhat/rhn/domain/errata/Cve.java
@@ -13,6 +13,10 @@
* in this software or its documentation.
*/
package com.redhat.rhn.domain.errata;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
/**
*
*
@@ -48,4 +52,28 @@ public void setName(String nameIn) {
public String getName() {
return name;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof Cve)) {
+ return false;
+ }
+ Cve castOther = (Cve) other;
+ return new EqualsBuilder()
+ .append(id, castOther.id)
+ .isEquals();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(id)
+ .toHashCode();
+ }
}
diff --git a/java/code/src/com/redhat/rhn/domain/rhnpackage/PackageType.java b/java/code/src/com/redhat/rhn/domain/rhnpackage/PackageType.java
index f551c8fbcf72..db0d9439fef9 100644
--- a/java/code/src/com/redhat/rhn/domain/rhnpackage/PackageType.java
+++ b/java/code/src/com/redhat/rhn/domain/rhnpackage/PackageType.java
@@ -14,6 +14,8 @@
*/
package com.redhat.rhn.domain.rhnpackage;
+import java.util.EnumSet;
+
public enum PackageType {
RPM("rpm"),
DEB("deb");
@@ -27,4 +29,10 @@ public enum PackageType {
public String getDbString() {
return dbString;
}
+
+ public static PackageType fromDbString(String dbString) {
+ return EnumSet.allOf(PackageType.class).stream()
+ .filter(packageType -> packageType.dbString.equals(dbString))
+ .findAny().orElseThrow();
+ }
}
diff --git a/java/code/src/com/redhat/rhn/domain/server/Server.java b/java/code/src/com/redhat/rhn/domain/server/Server.java
index 915226c97825..8ceb80467ad9 100644
--- a/java/code/src/com/redhat/rhn/domain/server/Server.java
+++ b/java/code/src/com/redhat/rhn/domain/server/Server.java
@@ -139,6 +139,8 @@ public class Server extends BaseDomainHelper implements Identifiable {
private MaintenanceSchedule maintenanceSchedule;
private Boolean hasConfigFeature;
+ private String cpe;
+
public static final String VALID_CNAMES = "valid_cnames_";
/**
@@ -2459,4 +2461,21 @@ public void setOsFamily(String osFamilyIn) {
this.osFamily = osFamilyIn;
}
+ /**
+ * Getter for CPE (Common Platform Enumeration)
+ *
+ * @return cpe
+ * */
+ public String getCpe() {
+ return cpe;
+ }
+
+ /**
+ * Setter for CPE (Common Platform Enumeration)
+ *
+ * @param cpeIn to set
+ * */
+ public void setCpe(String cpeIn) {
+ this.cpe = cpeIn;
+ }
}
diff --git a/java/code/src/com/redhat/rhn/domain/server/Server_legacyUser.hbm.xml b/java/code/src/com/redhat/rhn/domain/server/Server_legacyUser.hbm.xml
index a906cdfb8801..47f446a06513 100644
--- a/java/code/src/com/redhat/rhn/domain/server/Server_legacyUser.hbm.xml
+++ b/java/code/src/com/redhat/rhn/domain/server/Server_legacyUser.hbm.xml
@@ -15,6 +15,7 @@ PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ = 0;
+
+ status = isPackagePatched ? Status.PATCHED : Status.VULNERABLE;
+ }
+
+ public enum Status {
+ PATCHED, VULNERABLE
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAffectedServer.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAffectedServer.java
new file mode 100644
index 000000000000..a6b64cf4a754
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAffectedServer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 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.redhat.rhn.frontend.xmlrpc.audit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A server that is affected by a CVE
+ * */
+public class CVEAffectedServer {
+ private Long id;
+ private String name;
+ private List affectedPackages;
+
+ public CVEAffectedServer(Long id, String name, List affectedPackages) {
+ this.id = id;
+ this.name = name;
+ this.affectedPackages = new ArrayList<>(affectedPackages);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getAffectedPackages() {
+ return affectedPackages;
+ }
+
+ public void setAffectedPackages(List affectedPackages) {
+ this.affectedPackages = affectedPackages;
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAuditHandler.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAuditHandler.java
index 5479fac18803..d6a9f22202d2 100644
--- a/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAuditHandler.java
+++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/audit/CVEAuditHandler.java
@@ -14,22 +14,29 @@
*/
package com.redhat.rhn.frontend.xmlrpc.audit;
+import static java.util.stream.Collectors.groupingBy;
+
import com.redhat.rhn.FaultException;
+import com.redhat.rhn.domain.rhnpackage.PackageType;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.xmlrpc.BaseHandler;
import com.redhat.rhn.frontend.xmlrpc.MethodInvalidParamException;
import com.redhat.rhn.frontend.xmlrpc.UnknownCVEIdentifierFaultException;
+import com.redhat.rhn.manager.audit.CVEAffectedPackageItem;
import com.redhat.rhn.manager.audit.CVEAuditImage;
-import com.redhat.rhn.manager.audit.CVEAuditManager;
+import com.redhat.rhn.manager.audit.CVEAuditManagerOVAL;
import com.redhat.rhn.manager.audit.CVEAuditServer;
import com.redhat.rhn.manager.audit.PatchStatus;
import com.redhat.rhn.manager.audit.UnknownCVEIdentifierException;
import com.suse.manager.api.ReadOnly;
+import com.suse.oval.OVALCachingFactory;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* CVESearchHandler
@@ -57,7 +64,7 @@ public class CVEAuditHandler extends BaseHandler {
*/
@ReadOnly
public List listSystemsByPatchStatus(User loggedInUser,
- String cveIdentifier) {
+ String cveIdentifier) {
return listSystemsByPatchStatus(loggedInUser, cveIdentifier, null);
}
@@ -111,7 +118,7 @@ public List listSystemsByPatchStatus(User loggedInUser,
}
try {
- List result = CVEAuditManager.listSystemsByPatchStatus(
+ List result = CVEAuditManagerOVAL.listSystemsByPatchStatus(
loggedInUser, cveIdentifier, patchStatuses);
result.sort(Comparator.comparingInt(s -> s.getPatchStatus().getRank()));
@@ -177,7 +184,8 @@ public List listImagesByPatchStatus(User loggedInUser,
*/
@ReadOnly
public List listImagesByPatchStatus(User loggedInUser,
- String cveIdentifier, List patchStatusLabels) throws FaultException {
+ String cveIdentifier, List patchStatusLabels)
+ throws FaultException {
// Convert list of strings to patch status objects
EnumSet patchStatuses = EnumSet.noneOf(PatchStatus.class);
@@ -196,7 +204,7 @@ public List listImagesByPatchStatus(User loggedInUser,
}
try {
- List result = CVEAuditManager.listImagesByPatchStatus(
+ List result = CVEAuditManagerOVAL.listImagesByPatchStatus(
loggedInUser, cveIdentifier, patchStatuses);
result.sort(Comparator.comparingInt(i -> i.getPatchStatus().getRank()));
@@ -207,4 +215,84 @@ public List listImagesByPatchStatus(User loggedInUser,
throw new UnknownCVEIdentifierFaultException();
}
}
+
+ /**
+ * List visible systems with their corresponding affected packages regarding a given CVE identifier
+ *
+ * @param loggedInUser The current user
+ * @param cveIdentifier the CVE number to search for
+ *
+ * @apidoc.doc List visible systems with their corresponding affected packages regarding a given CVE identifier
+ * @apidoc.param #session_key()
+ * @apidoc.param #param("string", "cveIdentifier")
+ * @apidoc.returntype #return_array_begin() $CVEAffectedServerSerializer #array_end()
+ */
+ @ReadOnly
+ public List listAffectedSystems(User loggedInUser, String cveIdentifier) {
+ List affectedPackageItems =
+ OVALCachingFactory.listSystemsAffectedPackages(cveIdentifier);
+
+ return groupAffectedPackagesBySystemAndCollect(affectedPackageItems);
+ }
+
+
+ /**
+ * List known CVEs along the visible systems affected by the CVE with their corresponding affected packages
+ *
+ * @param loggedInUser The current user
+ *
+ * @apidoc.doc List known CVEs along the visible systems affected by the CVE with their corresponding
+ * affected packages
+ * @apidoc.param #session_key()
+ * @apidoc.returntype #return_array_begin() $CVEAffectedServerSerializer #array_end()
+ */
+ @ReadOnly
+ public Map> listAffectedSystemsByCve(User loggedInUser) {
+ List affectedPackageItems =
+ OVALCachingFactory.listSystemsAffectedPackages();
+
+ Map> affectedPackageItemsByCve =
+ affectedPackageItems.stream().collect(groupingBy(CVEAffectedPackageItem::getCve));
+
+ return affectedPackageItemsByCve.entrySet()
+ .stream().map(entry -> {
+ String cve = entry.getKey();
+ List cveAffectedPackageItems = entry.getValue();
+
+ List affectedServers =
+ groupAffectedPackagesBySystemAndCollect(cveAffectedPackageItems);
+
+ return Map.entry(cve, affectedServers);
+ }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ private static List groupAffectedPackagesBySystemAndCollect(
+ List affectedPackageItems) {
+ Map> affectedPackageItemsBySystem = affectedPackageItems.stream()
+ .collect(groupingBy(CVEAffectedPackageItem::getSystemId));
+
+ return affectedPackageItemsBySystem.entrySet().stream()
+ .map(entry -> {
+ Long systemId = entry.getKey();
+ List systemAffectedPackageItems = entry.getValue();
+
+ String systemName = systemAffectedPackageItems.get(0).getSystemName();
+
+ List affectedPackages = systemAffectedPackageItems
+ .stream()
+ .map(CVEAuditHandler::toAffectedPackage).collect(Collectors.toList());
+
+ return new CVEAffectedServer(systemId, systemName, affectedPackages);
+
+ }).collect(Collectors.toList());
+ }
+
+ private static CVEAffectedPackage toAffectedPackage(CVEAffectedPackageItem packageItem) {
+ String packageName = packageItem.getPackageName();
+ PackageType packageType = packageItem.getPackageType();
+ String installedVersion = packageItem.getInstalledPackageEvr().toUniversalEvrString();
+ String patchedVersion = packageItem.getPatchedVersion();
+
+ return new CVEAffectedPackage(packageName, installedVersion, patchedVersion, packageType);
+ }
}
diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/CVEAffectedPackageSerializer.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/CVEAffectedPackageSerializer.java
new file mode 100644
index 000000000000..afc5fdabe5fd
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/CVEAffectedPackageSerializer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 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.redhat.rhn.frontend.xmlrpc.serializer;
+
+import com.redhat.rhn.frontend.xmlrpc.audit.CVEAffectedPackage;
+
+import com.suse.manager.api.ApiResponseSerializer;
+import com.suse.manager.api.SerializationBuilder;
+import com.suse.manager.api.SerializedApiResponse;
+
+public class CVEAffectedPackageSerializer extends ApiResponseSerializer {
+ @Override
+ public SerializedApiResponse serialize(CVEAffectedPackage src) {
+ return new SerializationBuilder()
+ .add("name", src.getPackageName())
+ .add("installed_version", src.getInstalledVersion())
+ .add("patched_version", src.getPatchedVersion())
+ .add("patch_status", src.getStatus().toString())
+ .build();
+ }
+
+ @Override
+ public Class getSupportedClass() {
+ return CVEAffectedPackage.class;
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/CVEAffectedServerSerializer.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/CVEAffectedServerSerializer.java
new file mode 100644
index 000000000000..14aec54da641
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/CVEAffectedServerSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2023 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.redhat.rhn.frontend.xmlrpc.serializer;
+
+import com.redhat.rhn.frontend.xmlrpc.audit.CVEAffectedServer;
+
+import com.suse.manager.api.ApiResponseSerializer;
+import com.suse.manager.api.SerializationBuilder;
+import com.suse.manager.api.SerializedApiResponse;
+
+public class CVEAffectedServerSerializer extends ApiResponseSerializer {
+ @Override
+ public SerializedApiResponse serialize(CVEAffectedServer src) {
+ return new SerializationBuilder()
+ .add("system_id", src.getId())
+ .add("system_name", src.getName())
+ .add("affected_packages", src.getAffectedPackages())
+ .build();
+ }
+
+ @Override
+ public Class getSupportedClass() {
+ return CVEAffectedServer.class;
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/SerializerRegistry.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/SerializerRegistry.java
index a29a006de06e..70cd399239e9 100644
--- a/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/SerializerRegistry.java
+++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/serializer/SerializerRegistry.java
@@ -160,6 +160,8 @@ private SerializerRegistry() {
SERIALIZER_CLASSES.add(SystemEventDtoSerializer.class);
SERIALIZER_CLASSES.add(SystemEventDetailsDtoSerializer.class);
SERIALIZER_CLASSES.add(PaygSshDataSerializer.class);
+ SERIALIZER_CLASSES.add(CVEAffectedPackageSerializer.class);
+ SERIALIZER_CLASSES.add(CVEAffectedServerSerializer.class);
}
/**
diff --git a/java/code/src/com/redhat/rhn/manager/audit/CVEAffectedPackageItem.java b/java/code/src/com/redhat/rhn/manager/audit/CVEAffectedPackageItem.java
new file mode 100644
index 000000000000..cb0857684274
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/manager/audit/CVEAffectedPackageItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2023 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.redhat.rhn.manager.audit;
+
+import com.redhat.rhn.domain.rhnpackage.PackageEvr;
+import com.redhat.rhn.domain.rhnpackage.PackageType;
+
+public class CVEAffectedPackageItem {
+ private Long systemId;
+ private String systemName;
+ private String packageName;
+ private PackageType packageType;
+ private String patchedVersion;
+ private PackageEvr installedPackageEvr;
+ private String cve;
+
+ /**
+ * Standard constructor
+ * */
+ public CVEAffectedPackageItem(Long systemId, String systemName, String packageName, PackageType packageType, String patchedVersion,
+ String installedEpoch, String installedVersion, String installedRelease, String cveIn) {
+ this.systemId = systemId;
+ this.systemName = systemName;
+ this.packageName = packageName;
+ this.packageType = packageType;
+ this.patchedVersion = patchedVersion;
+ this.installedPackageEvr = new PackageEvr(installedEpoch, installedVersion, installedRelease, packageType);
+ cve = cveIn;
+ }
+
+ public Long getSystemId() {
+ return systemId;
+ }
+
+ public void setSystemId(Long systemId) {
+ this.systemId = systemId;
+ }
+
+ public String getSystemName() {
+ return systemName;
+ }
+
+ public void setSystemName(String systemName) {
+ this.systemName = systemName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public PackageType getPackageType() {
+ return packageType;
+ }
+
+ public void setPackageType(PackageType packageType) {
+ this.packageType = packageType;
+ }
+
+ public String getPatchedVersion() {
+ return patchedVersion;
+ }
+
+ public void setPatchedVersion(String patchedVersion) {
+ this.patchedVersion = patchedVersion;
+ }
+
+ public PackageEvr getInstalledPackageEvr() {
+ return installedPackageEvr;
+ }
+
+ public void setInstalledPackageEvr(PackageEvr installedPackageEvr) {
+ this.installedPackageEvr = installedPackageEvr;
+ }
+
+ public String getCve() {
+ return cve;
+ }
+
+ public void setCve(String cveIn) {
+ cve = cveIn;
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManager.java b/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManager.java
index 43088314f8e8..aa03c2e8fcd1 100644
--- a/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManager.java
+++ b/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManager.java
@@ -87,7 +87,7 @@ public class CVEAuditManager {
new HashMap<>();
/** Magic number signalling a patch present in a product migration channel */
- private static final int SUCCESSOR_PRODUCT_RANK_BOUNDARY = 50_000;
+ public static final int SUCCESSOR_PRODUCT_RANK_BOUNDARY = 50_000;
/** Magic number signalling a patch present in a product predecessor channel */
private static final int PREDECESSOR_PRODUCT_RANK_BOUNDARY = 100_000;
@@ -129,7 +129,7 @@ public static void insertRelevantImageChannels(Map addRelevantMigrationProductChannels(
* @return the channel ID
*/
public static Long getBaseProductChannelId(SUSEProductDto baseProduct,
- ChannelArch arch) {
+ ChannelArch arch) {
Long baseProductID = baseProduct.getId();
if (baseProductID != null) {
EssentialChannelDto channel =
@@ -604,7 +604,7 @@ public static Long getBaseProductChannelId(SUSEProductDto baseProduct,
* - contained in a certain channel
* in order to detect if the system is affected or not by a certain CVE
*/
- private static class CVEPatchStatus {
+ public static class CVEPatchStatus {
private final long systemId;
private final String systemName;
@@ -803,8 +803,15 @@ Otherwise, all values will be null (no EVR present)
}
- private static Stream listSystemsByPatchStatus(User user,
- String cveIdentifier) {
+ /**
+ * List visible systems with their patch status regarding a given CVE identifier.
+ *
+ * @param user the calling user
+ * @param cveIdentifier the CVE identifier to lookup
+ * @return list of system records with patch status
+ */
+ public static Stream listSystemsByPatchStatus(User user,
+ String cveIdentifier) {
SelectMode m = ModeFactory.getMode("cve_audit_queries",
"list_systems_by_patch_status");
@@ -844,7 +851,6 @@ Otherwise, all values will be null (no EVR present)
}
-
/**
* List visible systems with their patch status regarding a given CVE identifier.
*
@@ -937,91 +943,7 @@ private static List listSystemsByPatchStatus(List> systemResultMap : resultsBySystem.entrySet()) {
- CVEAuditSystemBuilder system = new CVEAuditSystemBuilder(systemResultMap.getKey());
- List systemResults = systemResultMap.getValue();
- system.setSystemName(systemResults.get(0).getSystemName());
-
- // The relevant channels assigned to the system
- Set assignedChannels = new HashSet<>();
-
- // Group results for the system further by package names, filtering out 'not-affected' entries
- Map> resultsByPackage =
- systemResults.stream().filter(r -> r.getErrataId().isPresent())
- .filter(r -> r.getChannelRank().orElse(0L) < PREDECESSOR_PRODUCT_RANK_BOUNDARY)
- .collect(Collectors.groupingBy(r -> r.getPackageName().get()));
-
- // When live patching is available, the original kernel packages ('-default' or '-xen') must be ignored.
- // Keep a list of package names to be ignored.
- Set livePatchedPackages = resultsByPackage.keySet().stream()
- .map(p -> Pattern.compile("^(?:kgraft-patch|kernel-livepatch)-.*-([^-]*)$").matcher(p))
- .filter(Matcher::matches).map(m -> "kernel-" + m.group(1)).collect(Collectors.toSet());
-
- AtomicBoolean patchInSuccessorProduct = new AtomicBoolean(false);
- AtomicBoolean patchesInstalled = new AtomicBoolean(false);
- Set successorErratas = new HashSet<>();
-
- // Loop through affected packages one by one
- for (Map.Entry> packageResults : resultsByPackage.entrySet()) {
- if (livePatchedPackages.contains(packageResults.getKey())) {
- // This package is substituted with live patching, ignore it
- continue;
- }
-
- // Get the result row with the top ranked channel containing the package,
- // or empty if the package is already patched
- Optional patchCandidateResult = getPatchCandidateResult(packageResults.getValue());
-
- patchCandidateResult.ifPresentOrElse(result -> {
- // The package is not patched. Keep a list of the missing patch and the top candidate channel
- AuditChannelInfo channel = new AuditChannelInfo(result.getChannelId().get(),
- result.getChannelName(), result.getChannelLabel(), result.getChannelRank().orElse(0L));
- ErrataIdAdvisoryPair errata = new ErrataIdAdvisoryPair(result.getErrataId().get(),
- result.getErrataAdvisory());
- system.addErrata(errata);
- system.addChannel(channel);
-
- if (result.isChannelAssigned()) {
- assignedChannels.add(channel);
- }
- else if (result.getChannelRank().get() >= SUCCESSOR_PRODUCT_RANK_BOUNDARY) {
- patchInSuccessorProduct.set(true);
- successorErratas.add(errata);
- }
- }, () -> {
- patchesInstalled.set(true);
- });
- }
-
- boolean allChannelsForOneErrataAssigned = assignedChannels.containsAll(system.getChannels());
- // Filter out channels that are part of a successor or predecessor product. This is to make sure the
- // current product is chosen as the most suitable candidate if there is a patch available for it or
- // a patch is already installed, even though it might not contain a patch for all the packages e.g.
- // because some versions are to old to be affected.
- if (patchInSuccessorProduct.get()) {
- Set erratasNotInSuccessor = system.getErratas().stream().filter(errata ->
- !successorErratas.contains(errata)).collect(Collectors.toSet());
- Set filteredChannels = system.getChannels().stream().filter(channel ->
- channel.getRank() < SUCCESSOR_PRODUCT_RANK_BOUNDARY).collect(Collectors.toSet());
-
- // If there are no erratas found that are not part of a successor product and there are already
- // patches installed we assume that the system is already patched
- if (erratasNotInSuccessor.isEmpty() && patchesInstalled.get()) {
- system.setChannels(Collections.emptySet());
- system.setErratas(Collections.emptySet());
- }
- else if (!filteredChannels.isEmpty()) {
- allChannelsForOneErrataAssigned = assignedChannels.containsAll(filteredChannels);
- if (allChannelsForOneErrataAssigned) {
- // Don't display the patches and channels that belong to successor products
- system.setChannels(filteredChannels);
- system.setErratas(erratasNotInSuccessor);
- }
- }
- }
-
- system.setPatchStatus(getPatchStatus(system.getErratas().isEmpty(),
- allChannelsForOneErrataAssigned, !resultsByPackage.isEmpty(),
- patchInSuccessorProduct.get()));
+ CVEAuditSystemBuilder system = doAuditSystem(systemResultMap.getKey(), systemResultMap.getValue());
// Check if the patch status is contained in the filter
if (patchStatuses.contains(system.getPatchStatus())) {
@@ -1033,6 +955,104 @@ else if (!filteredChannels.isEmpty()) {
return ret;
}
+ /**
+ * Audit the given system with an id {@code systemId} based on Channels data exclusively.
+ *
+ * @param systemId the id of the system to audit
+ * @param systemResults list produced by {@link CVEAuditManager#listSystemsByPatchStatus(User, String)},
+ * helpful for determining patch status and relevant channels and erratas
+ * @return a record with data about a single system containing that system's patch status regarding a certain
+ * given CVE identifier as well as sets of relevant channels and erratas.
+ */
+ public static CVEAuditSystemBuilder doAuditSystem(Long systemId, List systemResults) {
+ CVEAuditSystemBuilder system = new CVEAuditSystemBuilder(systemId);
+ system.setSystemName(systemResults.get(0).getSystemName());
+
+ // The relevant channels assigned to the system
+ Set assignedChannels = new HashSet<>();
+
+ // Group results for the system further by package names, filtering out 'not-affected' entries
+ Map> resultsByPackage =
+ systemResults.stream().filter(r -> r.getErrataId().isPresent())
+ .filter(r -> r.getChannelRank().orElse(0L) < PREDECESSOR_PRODUCT_RANK_BOUNDARY)
+ .collect(Collectors.groupingBy(r -> r.getPackageName().get()));
+
+ // When live patching is available, the original kernel packages ('-default' or '-xen') must be ignored.
+ // Keep a list of package names to be ignored.
+ Set livePatchedPackages = resultsByPackage.keySet().stream()
+ .map(p -> Pattern.compile("^(?:kgraft-patch|kernel-livepatch)-.*-([^-]*)$").matcher(p))
+ .filter(Matcher::matches).map(m -> "kernel-" + m.group(1)).collect(Collectors.toSet());
+
+ AtomicBoolean patchInSuccessorProduct = new AtomicBoolean(false);
+ AtomicBoolean patchesInstalled = new AtomicBoolean(false);
+ Set successorErratas = new HashSet<>();
+
+ // Loop through affected packages one by one
+ for (Map.Entry> packageResults : resultsByPackage.entrySet()) {
+ if (livePatchedPackages.contains(packageResults.getKey())) {
+ // This package is substituted with live patching, ignore it
+ continue;
+ }
+
+ // Get the result row with the top ranked channel containing the package,
+ // or empty if the package is already patched
+ Optional patchCandidateResult = getPatchCandidateResult(packageResults.getValue());
+
+ patchCandidateResult.ifPresentOrElse(result -> {
+ // The package is not patched. Keep a list of the missing patch and the top candidate channel
+ AuditChannelInfo channel = new AuditChannelInfo(result.getChannelId().get(),
+ result.getChannelName(), result.getChannelLabel(), result.getChannelRank().orElse(0L));
+ ErrataIdAdvisoryPair errata = new ErrataIdAdvisoryPair(result.getErrataId().get(),
+ result.getErrataAdvisory());
+ system.addErrata(errata);
+ system.addChannel(channel);
+
+ if (result.isChannelAssigned()) {
+ assignedChannels.add(channel);
+ }
+ else if (result.getChannelRank().get() >= SUCCESSOR_PRODUCT_RANK_BOUNDARY) {
+ patchInSuccessorProduct.set(true);
+ successorErratas.add(errata);
+ }
+ }, () -> {
+ patchesInstalled.set(true);
+ });
+ }
+
+ boolean allChannelsForOneErrataAssigned = assignedChannels.containsAll(system.getChannels());
+ // Filter out channels that are part of a successor or predecessor product. This is to make sure the
+ // current product is chosen as the most suitable candidate if there is a patch available for it or
+ // a patch is already installed, even though it might not contain a patch for all the packages e.g.
+ // because some versions are to old to be affected.
+ if (patchInSuccessorProduct.get()) {
+ Set erratasNotInSuccessor = system.getErratas().stream().filter(errata ->
+ !successorErratas.contains(errata)).collect(Collectors.toSet());
+ Set filteredChannels = system.getChannels().stream().filter(channel ->
+ channel.getRank() < SUCCESSOR_PRODUCT_RANK_BOUNDARY).collect(Collectors.toSet());
+
+ // If there are no erratas found that are not part of a successor product and there are already
+ // patches installed we assume that the system is already patched
+ if (erratasNotInSuccessor.isEmpty() && patchesInstalled.get()) {
+ system.setChannels(Collections.emptySet());
+ system.setErratas(Collections.emptySet());
+ }
+ else if (!filteredChannels.isEmpty()) {
+ allChannelsForOneErrataAssigned = assignedChannels.containsAll(filteredChannels);
+ if (allChannelsForOneErrataAssigned) {
+ // Don't display the patches and channels that belong to successor products
+ system.setChannels(filteredChannels);
+ system.setErratas(erratasNotInSuccessor);
+ }
+ }
+ }
+
+ system.setPatchStatus(getPatchStatus(system.getErratas().isEmpty(),
+ allChannelsForOneErrataAssigned, !resultsByPackage.isEmpty(),
+ patchInSuccessorProduct.get()));
+
+ return system;
+ }
+
/**
* Finds the best candidate channel among the CVE query results for a single
* package, or none if the package is already fully patched
@@ -1041,7 +1061,7 @@ else if (!filteredChannels.isEmpty()) {
* @return best candidate channel result for a patch on the specified package,
* or empty if the package is already patched
*/
- private static Optional getPatchCandidateResult(List packageResults) {
+ protected static Optional getPatchCandidateResult(List packageResults) {
Comparator evrComparator = Comparator.comparing(r -> r.getPackageEvr().get());
Optional latestInstalled = packageResults.stream()
@@ -1129,7 +1149,7 @@ public static PatchStatus getPatchStatus(
return PatchStatus.PATCHED;
}
else if (allChannelsForOneErrataAssigned) {
- return PatchStatus.AFFECTED_PATCH_APPLICABLE;
+ return PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE;
}
else if (patchInSuccessorProduct) {
return PatchStatus.AFFECTED_PATCH_INAPPLICABLE_SUCCESSOR_PRODUCT;
diff --git a/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManagerOVAL.java b/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManagerOVAL.java
new file mode 100644
index 000000000000..e13e88dd0315
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/manager/audit/CVEAuditManagerOVAL.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2023 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.redhat.rhn.manager.audit;
+
+
+import static com.redhat.rhn.manager.audit.CVEAuditManager.SUCCESSOR_PRODUCT_RANK_BOUNDARY;
+
+import com.redhat.rhn.domain.rhnpackage.PackageEvr;
+import com.redhat.rhn.domain.server.Server;
+import com.redhat.rhn.domain.user.User;
+import com.redhat.rhn.manager.rhnpackage.PackageManager;
+
+import com.suse.oval.OVALCachingFactory;
+import com.suse.oval.ShallowSystemPackage;
+import com.suse.oval.vulnerablepkgextractor.VulnerablePackage;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * This class, same as {@link CVEAuditManager}, provides the functionality of CVE auditing.It bases its evaluation on
+ * OVAL data, in addition to channels data. Therefore, it provides more accurate results.
+ *
+ * We can't get rid of {@link CVEAuditManager} yet because not all supported Linux distributions provide
+ * OVAL vulnerability definitions, thus, we fall back to {@link CVEAuditManager} in that case.
+ *
+ */
+public class CVEAuditManagerOVAL {
+
+ private static final Logger LOG = LogManager.getLogger(CVEAuditManagerOVAL.class);
+
+ private CVEAuditManagerOVAL() {
+
+ }
+
+ /**
+ * List visible systems with their patch status regarding a given CVE identifier.
+ *
+ * @param user the calling user
+ * @param cveIdentifier the CVE identifier to lookup
+ * @param patchStatuses the patch statuses
+ * @return list of system records with patch status
+ * @throws UnknownCVEIdentifierException if the CVE number is not known
+ */
+ public static List listSystemsByPatchStatus(User user, String cveIdentifier,
+ EnumSet patchStatuses)
+ throws UnknownCVEIdentifierException {
+ if (isCVEIdentifierUnknown(cveIdentifier)) {
+ throw new UnknownCVEIdentifierException();
+ }
+
+ List result = new ArrayList<>();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cveIdentifier)
+ .collect(Collectors.toList());
+
+ // Group the results by system
+ Map> resultsBySystem =
+ results.stream().collect(Collectors.groupingBy(CVEAuditManager.CVEPatchStatus::getSystemId));
+
+ Set clients = user.getServers();
+ for (Server clientServer : clients) {
+ CVEAuditSystemBuilder systemAuditResult;
+ // We need this initially to be able to get errata and audit channels information for the OVAL
+ // implementation.
+ // TODO: We could make a custom query that would get us only the data we're interested in instead of relying
+ // on CVEAuditManager#doAuditSystem implementation.
+
+ CVEAuditSystemBuilder auditWithChannelsResult =
+ CVEAuditManager.doAuditSystem(clientServer.getId(), resultsBySystem.get(clientServer.getId()));
+
+ if (doesSupportOVALAuditing(clientServer)) {
+ systemAuditResult = doAuditSystem(cveIdentifier, resultsBySystem.get(clientServer.getId()),
+ clientServer);
+ systemAuditResult.setChannels(auditWithChannelsResult.getChannels());
+ systemAuditResult.setErratas(auditWithChannelsResult.getErratas());
+ }
+ else {
+ systemAuditResult = auditWithChannelsResult;
+ }
+
+ if (patchStatuses.contains(systemAuditResult.getPatchStatus())) {
+ result.add(new CVEAuditServer(
+ systemAuditResult.getId(),
+ systemAuditResult.getSystemName(),
+ systemAuditResult.getPatchStatus(),
+ systemAuditResult.getChannels(),
+ systemAuditResult.getErratas()));
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isCVEIdentifierUnknown(String cveIdentifier) {
+ return !OVALCachingFactory.canAuditCVE(cveIdentifier);
+ }
+
+ /**
+ * Check if server support OVAL CVE auditing
+ *
+ * @param clientServer the server to check
+ * @return {@code True}
+ * */
+ public static boolean doesSupportOVALAuditing(Server clientServer) {
+ // TODO: check if OVAL is synced and client product is support .e.g. Red Hat, Debian, Ubuntu or SUSE
+ return true;
+ }
+
+ /**
+ * Audit the given {@code clientServer} regarding the given CVE identifier based on OVAL and Channels data.
+ *
+ * @param clientServer the server to audit
+ * @param results list produced by {@link CVEAuditManager#listSystemsByPatchStatus(User, String)},
+ * helpful for determining the availability of a patch for the vulnerability in channels.
+ * @param cveIdentifier the CVE identifier
+ * @return a record with data about a single system containing that system's patch status regarding a certain
+ * given CVE identifier as well as sets of relevant channels and erratas.
+ * */
+ public static CVEAuditSystemBuilder doAuditSystem(String cveIdentifier,
+ List results,
+ Server clientServer) {
+ // It's possible to have 2 or more patches for one package. It's necessary to apply all of them because
+ // they will have the same outcome i.e. patch the package; instead we need to choose only one.
+ // To choose the one, we rank patches based on the channel they come from .e.g.
+ // assigned, successor product, etc. And for each vulnerable package we keep only the highest ranking patch
+ results = keepOnlyPatchCandidates(results);
+
+ CVEAuditSystemBuilder cveAuditServerBuilder = new CVEAuditSystemBuilder(clientServer.getId());
+ cveAuditServerBuilder.setSystemName(clientServer.getName());
+
+ List allInstalledPackages =
+ PackageManager.shallowSystemPackageList(clientServer.getId());
+
+ LOG.error("Vul packages before filtering: {}",
+ OVALCachingFactory.getVulnerablePackagesByProductAndCve(clientServer.getCpe(), cveIdentifier));
+
+ Set clientProductVulnerablePackages =
+ OVALCachingFactory.getVulnerablePackagesByProductAndCve(clientServer.getCpe(), cveIdentifier).stream()
+ .filter(pkg -> isPackageInstalled(pkg, allInstalledPackages))
+ .collect(Collectors.toSet());
+
+ LOG.error("Vul packages: {}", clientProductVulnerablePackages);
+
+ if (clientProductVulnerablePackages.isEmpty()) {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.NOT_AFFECTED);
+ return cveAuditServerBuilder;
+ }
+
+ Set patchedVulnerablePackages = clientProductVulnerablePackages.stream()
+ .filter(vulnerablePackage -> vulnerablePackage.getFixVersion().isPresent()).collect(
+ Collectors.toSet());
+
+ Set unpatchedVulnerablePackages = clientProductVulnerablePackages.stream()
+ .filter(vulnerablePackage -> vulnerablePackage.getFixVersion().isEmpty()).collect(
+ Collectors.toSet());
+
+ boolean allPackagesUnpatched = unpatchedVulnerablePackages.size() == clientProductVulnerablePackages.size();
+
+ if (allPackagesUnpatched) {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.AFFECTED_PATCH_UNAVAILABLE);
+ }
+ else {
+ boolean allPackagesPatched = patchedVulnerablePackages.stream().allMatch(patchedPackage ->
+ allInstalledPackages.stream()
+ .filter(installedPackage ->
+ Objects.equals(installedPackage.getName(), patchedPackage.getName()))
+ .anyMatch(installedPackage ->
+ installedPackage.getPackageEVR()
+ .compareTo(PackageEvr.parseRpm(patchedPackage.getFixVersion().get())) >= 0));
+
+ if (allPackagesPatched) {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.PATCHED);
+ }
+ else {
+ List patchesInAssignedChannels = results.stream()
+ .filter(CVEAuditManager.CVEPatchStatus::isChannelAssigned)
+ .collect(Collectors.toList());
+
+ List patchesInUnassignedChannels = results.stream()
+ .filter(cvePatchStatus -> !cvePatchStatus.isChannelAssigned())
+ .collect(Collectors.toList());
+
+ long numberOfPackagesWithPatchInAssignedChannels =
+ patchedVulnerablePackages.stream().filter(patchedPackage -> patchesInAssignedChannels
+ .stream()
+ .anyMatch(patch ->
+ patch.getPackageName().equals(Optional.of(patchedPackage.getName()))
+ )
+ ).count();
+
+ boolean allPackagesHavePatchInAssignedChannels =
+ numberOfPackagesWithPatchInAssignedChannels == patchedVulnerablePackages.size();
+ boolean somePackagesHavePatchInAssignedChannels = numberOfPackagesWithPatchInAssignedChannels > 0;
+
+ if (allPackagesHavePatchInAssignedChannels) {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE);
+ }
+ else if (somePackagesHavePatchInAssignedChannels) {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.AFFECTED_PARTIAL_PATCH_APPLICABLE);
+ }
+ else {
+ long numberOfPackagesWithPatchInUnassignedChannels =
+ patchedVulnerablePackages.stream().filter(patchedPackage -> patchesInUnassignedChannels
+ .stream()
+ .anyMatch(patch ->
+ patch.getPackageName().equals(Optional.of(patchedPackage.getName()))
+ )
+ ).count();
+
+ boolean somePackagesHavePatchInUnassignedChannels =
+ numberOfPackagesWithPatchInUnassignedChannels > 0 &&
+ numberOfPackagesWithPatchInUnassignedChannels == patchedVulnerablePackages.size();
+
+ boolean allPackagesHavePatchInUnassignedChannels =
+ numberOfPackagesWithPatchInUnassignedChannels == patchedVulnerablePackages.size();
+
+ if (allPackagesHavePatchInUnassignedChannels) {
+ boolean allPackagesHavePatchInSuccessorChannel = patchesInUnassignedChannels.stream()
+ .allMatch(patch ->
+ patch.getChannelRank().orElse(0L) >= SUCCESSOR_PRODUCT_RANK_BOUNDARY);
+ if (allPackagesHavePatchInSuccessorChannel) {
+ cveAuditServerBuilder
+ .setPatchStatus(PatchStatus.AFFECTED_PATCH_INAPPLICABLE_SUCCESSOR_PRODUCT);
+ }
+ else {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.AFFECTED_PATCH_INAPPLICABLE);
+ }
+ }
+ else if (somePackagesHavePatchInUnassignedChannels) {
+ //TODO: Not sure how to handle...
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.AFFECTED_PATCH_UNAVAILABLE_IN_UYUNI);
+ }
+ else {
+ cveAuditServerBuilder.setPatchStatus(PatchStatus.AFFECTED_PATCH_UNAVAILABLE_IN_UYUNI);
+ }
+ }
+ }
+ }
+
+ LOG.error("Patch Status: {}", cveAuditServerBuilder.getPatchStatus());
+
+ return cveAuditServerBuilder;
+ }
+
+ private static List keepOnlyPatchCandidates(
+ List results) {
+ List patchCandidates = new ArrayList<>();
+
+ Map> resultsByPackage = results.stream()
+ .filter(result -> result.getPackageName().isPresent())
+ .collect(Collectors.groupingBy(r -> r.getPackageName().get()));
+
+ for (String packageName : resultsByPackage.keySet()) {
+ List packageResults = resultsByPackage.get(packageName);
+ CVEAuditManager.getPatchCandidateResult(packageResults).ifPresent(patchCandidates::add);
+ }
+
+ return patchCandidates;
+ }
+
+ private static boolean isPackageInstalled(VulnerablePackage pkg, List allInstalledPackages) {
+ return allInstalledPackages.stream()
+ .anyMatch(installed -> Objects.equals(installed.getName(), pkg.getName()));
+ }
+
+ /**
+ * List visible images with their patch status regarding a given CVE identifier.
+ *
+ * @param user the calling user
+ * @param cveIdentifier the CVE identifier to lookup
+ * @param patchStatuses the patch statuses
+ * @return list of images records with patch status
+ * @throws UnknownCVEIdentifierException if the CVE number is not known
+ */
+ public static List listImagesByPatchStatus(User user,
+ String cveIdentifier, EnumSet patchStatuses)
+ throws UnknownCVEIdentifierException {
+ // TODO: Audit images with OVAL
+ return CVEAuditManager.listImagesByPatchStatus(user, cveIdentifier, patchStatuses);
+ }
+
+ /**
+ * Populate channels for CVE Audit
+ * */
+ public static void populateCVEChannels() {
+ CVEAuditManager.populateCVEChannels();
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/manager/audit/PatchStatus.java b/java/code/src/com/redhat/rhn/manager/audit/PatchStatus.java
index ac228f5dfbe8..c3d07ae33d4b 100644
--- a/java/code/src/com/redhat/rhn/manager/audit/PatchStatus.java
+++ b/java/code/src/com/redhat/rhn/manager/audit/PatchStatus.java
@@ -21,11 +21,14 @@
public enum PatchStatus {
// Values sorted by seriousness
- AFFECTED_PATCH_INAPPLICABLE("Affected, patch available in unassigned channel", 0),
- AFFECTED_PATCH_APPLICABLE("Affected, patch available in assigned channel", 1),
- NOT_AFFECTED("Not affected", 2),
- PATCHED("Patched", 3),
- AFFECTED_PATCH_INAPPLICABLE_SUCCESSOR_PRODUCT("Affected, patch available in a Product Migration target", 4);
+ AFFECTED_PATCH_UNAVAILABLE("Affected, patch is unavailable anywhere", 0),
+ AFFECTED_PATCH_UNAVAILABLE_IN_UYUNI("Affected, patch is unavailable in any of the synced channels", 1),
+ AFFECTED_PATCH_INAPPLICABLE("Affected, patch available in unassigned channel", 2),
+ AFFECTED_PARTIAL_PATCH_APPLICABLE("Affected, partial patch available in assigned channel", 3),
+ AFFECTED_FULL_PATCH_APPLICABLE("Affected, full patch available in assigned channel", 4),
+ NOT_AFFECTED("Not affected", 5),
+ PATCHED("Patched", 6),
+ AFFECTED_PATCH_INAPPLICABLE_SUCCESSOR_PRODUCT("Affected, patch available in a Product Migration target", 7);
/**
* The lower the more severe
diff --git a/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerOVALTest.java b/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerOVALTest.java
new file mode 100644
index 000000000000..a237026a37ff
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerOVALTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) 2023 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.redhat.rhn.manager.audit.test;
+
+import static com.redhat.rhn.domain.rhnpackage.test.PackageNameTest.createTestPackageName;
+import static com.redhat.rhn.testing.ErrataTestUtils.createLaterTestPackage;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestChannel;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestCve;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestErrata;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestInstalledPackage;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestPackage;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestServer;
+import static com.redhat.rhn.testing.ErrataTestUtils.createTestUser;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.redhat.rhn.common.hibernate.HibernateFactory;
+import com.redhat.rhn.domain.channel.Channel;
+import com.redhat.rhn.domain.errata.Cve;
+import com.redhat.rhn.domain.errata.Errata;
+import com.redhat.rhn.domain.rhnpackage.Package;
+import com.redhat.rhn.domain.server.Server;
+import com.redhat.rhn.domain.user.User;
+import com.redhat.rhn.manager.audit.CVEAuditManager;
+import com.redhat.rhn.manager.audit.CVEAuditManagerOVAL;
+import com.redhat.rhn.manager.audit.CVEAuditSystemBuilder;
+import com.redhat.rhn.manager.audit.PatchStatus;
+import com.redhat.rhn.manager.audit.RankedChannel;
+import com.redhat.rhn.manager.audit.UnknownCVEIdentifierException;
+import com.redhat.rhn.testing.RhnBaseTestCase;
+import com.redhat.rhn.testing.TestUtils;
+
+import com.suse.oval.OVALCachingFactory;
+import com.suse.oval.OVALCleaner;
+import com.suse.oval.OsFamily;
+import com.suse.oval.OvalParser;
+import com.suse.oval.ovaltypes.OvalRootType;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+// TODO: Test that if we get AFFECTED_PATCH_INAPPLICABLE auditServer.Channels and auditServer.Erratas are not null
+public class CVEAuditManagerOVALTest extends RhnBaseTestCase {
+ private static final Logger LOG = LogManager.getLogger(CVEAuditManagerOVALTest.class);
+ private OvalParser ovalParser = new OvalParser();
+
+ @Test
+ void testDoAuditSystemNotAffected() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.5"); // Not Leap 15.4
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.NOT_AFFECTED, systemAuditResult.getPatchStatus());
+ }
+
+ /**
+ * If the system's OS is flagged as vulnerable based on OVAL, it doesn't necessarily mean that the system
+ * is vulnerable. For example, if none of the vulnerable packages are installed on the system,
+ * it means that the system is by definition NOT_AFFECTED.
+ * This test verifies this scenario.
+ * */
+ @Test
+ void testDoAuditSystemNotAffectedWhenOSIsAffected() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4"); // openSUSE Leap 15.4, same as the affected OS in OVAL
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.NOT_AFFECTED, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ void testDoAuditSystemPatched() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ User user = createTestUser();
+
+ Channel channel = createTestChannel(user);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ Package unpatched = createTestPackage(user, channel, "noarch");
+ unpatched.setPackageName(createTestPackageName("kernel-debug-base"));
+ Package patched = createLaterTestPackage(user, null, channel, unpatched,
+ "0", "4.12.14", "150100.197.137.2");
+
+ createTestInstalledPackage(patched, server);
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.PATCHED, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ void testDoAuditSystemAffectedFullPatchAvailable() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ Package unpatched = createTestPackage(user, channel, "noarch",
+ "kernel-debug-base", "0", "4.12.13", "150100.197.137.2");
+
+ Package patched = createTestPackage(user, errata, channel, "noarch",
+ "kernel-debug-base", "0", "4.12.14", "150100.197.137.2");
+
+ createTestInstalledPackage(unpatched, server);
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, systemAuditResult.getPatchStatus());
+ }
+
+
+ @Test
+ void testDoAuditSystemAffectedPatchUnavailable() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-2.xml"));
+
+ Cve cve = createTestCve("CVE-2008-2934");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ Package affected = createTestPackage(user, channel, "noarch", "MozillaFirefox");
+ createTestPackage(user, channel, "noarch", "MozillaFirefox-devel");
+
+ createTestInstalledPackage(affected, server);
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.AFFECTED_PATCH_UNAVAILABLE, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ void testDoAuditSystemAffectedPartialPatchAvailable() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-3.xml"));
+
+ Cve cve = createTestCve("CVE-2008-2934");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ // Only package 'MozillaFirefox' has a patch in the assigned channels
+ createTestPackage(user, errata, channel, "noarch", "MozillaFirefox", "0", "2.4.0",
+ "150400.1.12");
+
+ Package unpatched1 =
+ createTestPackage(user, channel, "noarch", "MozillaFirefox", "0", "2.3.0",
+ "150400.1.12");
+ Package unpatched2 =
+ createTestPackage(user, channel, "noarch", "MozillaFirefox-devel", "0",
+ "2.3.0", "150400.1.12");
+
+ createTestInstalledPackage(unpatched1, server);
+ createTestInstalledPackage(unpatched2, server);
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.AFFECTED_PARTIAL_PATCH_APPLICABLE, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ void testDoAuditSystemAffectedPartialPatchAvailableFalsePositive() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-3.xml"));
+
+ Cve cve = createTestCve("CVE-2008-2934");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ createTestPackage(user, errata, channel, "noarch", "MozillaFirefox", "0", "2.4.0", "150400.1.12");
+ Package unpatched = createTestPackage(user, channel, "noarch", "MozillaFirefox", "0", "2.3.0", "150400.1.12");
+
+ // The 'MozillaFirefox-devel' package is identified as vulnerable based on the OVAL data,
+ // but it is not currently installed on the system.
+ // Although 'MozillaFirefox-devel' does not have a patch available in the assigned channels,
+ // the algorithm should return AFFECTED_FULL_PATCH_APPLICABLE instead of AFFECTED_PARTIAL_PATCH_APPLICABLE.
+ // This decision is made because the 'MozillaFirefox' package,
+ // which is installed, has a patch that can be applied.
+ createTestPackage(user, channel, "noarch", "MozillaFirefox-devel");
+
+ createTestInstalledPackage(unpatched, server);
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ void testDoAuditSystemAffectedPatchInapplicable() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user);
+
+ // This channel is not assigned to server
+ Channel otherChannel = createTestChannel(user, errata);
+ Set assignedChannels = Set.of(channel);
+ Server server = createTestServer(user, assignedChannels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ Package unpatched = createTestPackage(user, channel, "noarch",
+ "kernel-debug-base", "0", "4.12.13", "150100.197.137.2");
+
+ // Patch exists in an unassigned channel
+ createTestPackage(user, errata, otherChannel, "noarch",
+ "kernel-debug-base", "0", "4.12.14", "150100.197.137.2");
+
+ createTestInstalledPackage(unpatched, server);
+
+ CVEAuditManager.populateCVEChannels();
+ // We don't have to care about the internals of populateCVEChannels and weather it will assign otherChannel as
+ // a relevant channel to server; we add it ourselves.
+ RankedChannel otherChannelRanked = new RankedChannel(otherChannel.getId(), 0);
+ Map> relevantChannels = new HashMap<>();
+ relevantChannels.put(server, List.of(otherChannelRanked));
+ CVEAuditManager.insertRelevantServerChannels(relevantChannels);
+ HibernateFactory.getSession().flush();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.AFFECTED_PATCH_INAPPLICABLE, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ void testDoAuditSystemAffectedPatchInapplicableSuccessorProduct() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user);
+
+ // This channel is not assigned to server
+ Channel otherChannel = createTestChannel(user, errata);
+ Set assignedChannels = Set.of(channel);
+ Server server = createTestServer(user, assignedChannels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ Package unpatched = createTestPackage(user, channel, "noarch",
+ "kernel-debug-base", "0", "4.12.13", "150100.197.137.2");
+
+ // Patch exists in an unassigned channel
+ createTestPackage(user, errata, otherChannel, "noarch",
+ "kernel-debug-base", "0", "4.12.14", "150100.197.137.2");
+
+ createTestInstalledPackage(unpatched, server);
+
+ CVEAuditManager.populateCVEChannels();
+ // We set the rank to SUCCESSOR_PRODUCT_RANK_BOUNDARY in order to imply that it's a successor product
+ // migration channel
+ RankedChannel otherChannelRanked = new RankedChannel(otherChannel.getId(),
+ CVEAuditManager.SUCCESSOR_PRODUCT_RANK_BOUNDARY);
+ // We don't have to care about the internals of populateCVEChannels and weather it will assign otherChannel as
+ // a relevant channel to server; we add it ourselves.
+ Map> relevantChannels = new HashMap<>();
+ relevantChannels.put(server, List.of(otherChannelRanked));
+ CVEAuditManager.insertRelevantServerChannels(relevantChannels);
+ HibernateFactory.getSession().flush();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.AFFECTED_PATCH_INAPPLICABLE_SUCCESSOR_PRODUCT, systemAuditResult.getPatchStatus());
+ }
+
+ /**
+ * Verify that bnc#833783 is fixed:
+ * Test that irrelevant packages do not alter a system's PATCHED status
+ *
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void testDoAuditSystemPatchedWithIrrelevantErrata() throws Exception {
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve cve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ Set cves = Set.of(cve);
+ User user = createTestUser();
+
+ Errata errata = createTestErrata(user, cves);
+ Channel channel = createTestChannel(user, errata);
+ Set channels = Set.of(channel);
+
+ Server server = createTestServer(user, channels);
+ server.setCpe("cpe:/o:opensuse:leap:15.4");
+
+ Package unpatched = createTestPackage(user, channel, "noarch");
+ unpatched.setPackageName(createTestPackageName("kernel-debug-base"));
+ Package patched = createLaterTestPackage(user, errata, channel, unpatched,
+ "0", "4.12.14", "150100.197.137.2");
+
+ createTestInstalledPackage(patched, server);
+
+ CVEAuditManager.populateCVEChannels();
+
+ List results = CVEAuditManager.listSystemsByPatchStatus(user, cve.getName())
+ .collect(Collectors.toList());
+
+ CVEAuditSystemBuilder systemAuditResult = CVEAuditManagerOVAL.doAuditSystem(cve.getName(), results, server);
+
+ assertEquals(PatchStatus.PATCHED, systemAuditResult.getPatchStatus());
+ }
+
+ @Test
+ public void testListSystemsByPatchStatusUnknownCVE() {
+ String unknownCVE = TestUtils.randomString().substring(0, 13);
+ // Although we add the CVE to rhnCve, the exception should be still thrown because the CVE is not linked to any
+ // OVAL vulnerability in the database .i.e. not present in suseOVALPlatformVulnerablePackage
+ createTestCve(unknownCVE);
+
+ User user = createTestUser();
+
+ assertThrows(UnknownCVEIdentifierException.class,
+ () -> CVEAuditManagerOVAL.listSystemsByPatchStatus(user, unknownCVE,
+ EnumSet.allOf(PatchStatus.class)));
+ }
+
+ @Test
+ public void testListSystemsByPatchStatusKnownCVE() throws IOException, ClassNotFoundException {
+ User user = createTestUser();
+
+ OvalRootType ovalRoot = ovalParser.parse(TestUtils
+ .findTestData("/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml"));
+
+ Cve knownCve = createTestCve("CVE-2022-2991");
+
+ extractAndSaveVulnerablePackages(ovalRoot);
+
+ assertDoesNotThrow(() -> CVEAuditManagerOVAL.listSystemsByPatchStatus(user, knownCve.getName(),
+ EnumSet.allOf(PatchStatus.class)));
+ }
+
+ private static void extractAndSaveVulnerablePackages(OvalRootType rootType) {
+ OVALCleaner.cleanup(rootType, OsFamily.openSUSE_LEAP, "15.4");
+ OVALCachingFactory.savePlatformsVulnerablePackages(rootType);
+
+ HibernateFactory.getSession().flush();
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerTest.java b/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerTest.java
index 7922a281ccea..bc0a36e0dbd6 100644
--- a/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerTest.java
+++ b/java/code/src/com/redhat/rhn/manager/audit/test/CVEAuditManagerTest.java
@@ -333,11 +333,13 @@ public void testFindAllTargetProducts() throws Exception {
public void testSetPatchStatus() {
assertEquals(PatchStatus.PATCHED, CVEAuditManager.getPatchStatus(true, true, true, false));
assertEquals(PatchStatus.PATCHED, CVEAuditManager.getPatchStatus(true, false, true, false));
- assertEquals(PatchStatus.AFFECTED_PATCH_APPLICABLE, CVEAuditManager.getPatchStatus(false, true, true, false));
+ assertEquals(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE,
+ CVEAuditManager.getPatchStatus(false, true, true, false));
assertEquals(PatchStatus.AFFECTED_PATCH_INAPPLICABLE,
CVEAuditManager.getPatchStatus(false, false, true, false));
assertEquals(PatchStatus.NOT_AFFECTED, CVEAuditManager.getPatchStatus(false, false, false, false));
- assertEquals(PatchStatus.AFFECTED_PATCH_APPLICABLE, CVEAuditManager.getPatchStatus(false, true, true, true));
+ assertEquals(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE,
+ CVEAuditManager.getPatchStatus(false, true, true, true));
assertEquals(PatchStatus.AFFECTED_PATCH_INAPPLICABLE_SUCCESSOR_PRODUCT,
CVEAuditManager.getPatchStatus(false, false, true, true));
}
@@ -566,12 +568,12 @@ public void testListSystemsByPatchStatusAffectedPatchApplicable() throws Excepti
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results =
CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
// Everything is filtered except expected
- filter = EnumSet.of(PatchStatus.AFFECTED_PATCH_APPLICABLE);
+ filter = EnumSet.of(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE);
results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
// Only the expected result is filtered
filter = EnumSet.complementOf(filter);
@@ -711,7 +713,7 @@ public void testChannelOrderWithClonedChannels() throws Exception {
// Verify the order of returned channels
CVEAuditSystem result = findSystemRecord(server, results);
- assertEquals(PatchStatus.AFFECTED_PATCH_APPLICABLE, result.getPatchStatus());
+ assertEquals(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, result.getPatchStatus());
Iterator it = result.getChannels().iterator();
assertEquals((Long) channelClone.getId(), (Long) it.next().getId());
}
@@ -893,7 +895,7 @@ public void testPatchPartlyApplied() throws Exception {
// No filtering
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
}
/**
@@ -1140,7 +1142,7 @@ public void testLTSS() throws Exception {
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results =
CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server1, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server1, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
assertSystemPatchStatus(server2, PatchStatus.PATCHED, results);
assertSystemPatchStatus(server3, PatchStatus.PATCHED, results);
}
@@ -1268,7 +1270,7 @@ public void testAssignedChannelPreferredOverMigration() throws Exception {
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
List channels = new ArrayList<>(results.get(0).getChannels());
assertEquals(1, channels.size());
assertEquals(updateChannelSP2.getId().longValue(), channels.get(0).getId());
@@ -1342,7 +1344,7 @@ public void testIgnoreOldProductsWhenCurrentPatchAvailable() throws Exception {
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
}
/**
@@ -1411,7 +1413,7 @@ public void testIgnoreSuccessorProductsWhenCurrentPatchAvailable() throws Except
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
// Make sure the errata and channel for the successor product is filtered out
assertEquals(1, results.stream().findFirst().get().getErratas().size());
assertEquals(1, results.stream().findFirst().get().getChannels().size());
@@ -1533,7 +1535,7 @@ public void testSDK() throws Exception {
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server1, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server1, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
assertSystemPatchStatus(server2, PatchStatus.PATCHED, results);
}
@@ -1675,7 +1677,7 @@ public void testLivePatchingAffectedPatchApplicable() throws Exception {
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
}
/**
@@ -1749,7 +1751,7 @@ public void testLivePatchingAffectedPatchApplicableSles15() throws Exception {
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveName, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
}
/**
@@ -1900,7 +1902,7 @@ public void testLivePatchingAffectedOnlyKernelPatchApplicable() throws Exception
CVEAuditManager.populateCVEChannels();
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results = CVEAuditManager.listSystemsByPatchStatus(user, cveNameKernelExclusive, filter);
- assertSystemPatchStatus(server, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertSystemPatchStatus(server, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
Iterator it = results.get(0).getChannels().iterator();
assertEquals((Long) baseChannelClone.getId(), (Long) it.next().getId());
@@ -1982,12 +1984,12 @@ public void testListImagesByPatchStatusAffectedPatchApplicable() throws Exceptio
EnumSet filter = EnumSet.allOf(PatchStatus.class);
List results =
CVEAuditManager.listImagesByPatchStatus(user, cveName, filter);
- assertImagePatchStatus(image, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertImagePatchStatus(image, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
// Everything is filtered except expected
- filter = EnumSet.of(PatchStatus.AFFECTED_PATCH_APPLICABLE);
+ filter = EnumSet.of(PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE);
results = CVEAuditManager.listImagesByPatchStatus(user, cveName, filter);
- assertImagePatchStatus(image, PatchStatus.AFFECTED_PATCH_APPLICABLE, results);
+ assertImagePatchStatus(image, PatchStatus.AFFECTED_FULL_PATCH_APPLICABLE, results);
// Only the expected result is filtered
filter = EnumSet.complementOf(filter);
diff --git a/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml b/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml
new file mode 100644
index 000000000000..164766c5d664
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-1.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+ CVE-2022-2991
+
+ openSUSE Leap 15.4
+
+
+ A heap-based buffer overflow was found in the Linux kernel's LightNVM subsystem. The issue results
+ from the lack of proper validation of the length of user-supplied data prior to copying it to a
+ fixed-length heap-based buffer. This vulnerability allows a local attacker to escalate privileges
+ and execute arbitrary code in the context of the kernel. The attacker must first obtain the ability
+ to execute high-privileged code on the target system to exploit this vulnerability.
+
+
+
+
+ Important
+ CVE-2022-2991
+
+
+ cpe:/o:opensuse:leap:15.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ openSUSE-release
+
+
+
+ kernel-debug-base
+
+
+
+
+
+
+ 15.4
+
+
+ 0:4.12.14-150100.197.137.2
+
+
+
+
\ No newline at end of file
diff --git a/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-2.xml b/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-2.xml
new file mode 100644
index 000000000000..eb368a912a57
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-2.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+ CVE-2008-2934
+
+ SUSE Linux Enterprise Server 15 SP1-LTSS
+ SUSE Linux Enterprise Server for SAP Applications 15 SP1
+
+
+
+
+ Mozilla Firefox 3 before 3.0.1 on Mac OS X allows remote attackers to cause a denial of service (application crash) or possibly execute arbitrary code via a crafted GIF file that triggers a free of an uninitialized pointer.
+
+
+
+
+ Moderate
+ CVE-2008-2934
+ SUSE bug 407573
+
+ cpe:/o:suse:sles-ltss:15:sp1
+ cpe:/o:suse:sles_sap:15:sp1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ openSUSE-release
+
+
+
+ MozillaFirefox
+
+
+
+ MozillaFirefox-devel
+
+
+
+
+
+
+ 15.4
+
+
+
+ 0:0-0
+
+
+
+ 0:0-0
+
+
+
+
\ No newline at end of file
diff --git a/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-3.xml b/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-3.xml
new file mode 100644
index 000000000000..484a6528d28a
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/manager/audit/test/oval/oval-def-3.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+ CVE-2008-2934
+
+ SUSE Linux Enterprise Server 15 SP1-LTSS
+ SUSE Linux Enterprise Server for SAP Applications 15 SP1
+
+
+
+
+ Mozilla Firefox 3 before 3.0.1 on Mac OS X allows remote attackers to cause a denial of service (application crash) or possibly execute arbitrary code via a crafted GIF file that triggers a free of an uninitialized pointer.
+
+
+
+
+ Moderate
+ CVE-2008-2934
+ SUSE bug 407573
+
+ cpe:/o:suse:sles-ltss:15:sp1
+ cpe:/o:suse:sles_sap:15:sp1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ openSUSE-release
+
+
+
+ MozillaFirefox
+
+
+
+ MozillaFirefox-devel
+
+
+
+
+
+
+ 15.4
+
+
+
+ 0:2.4.0-150400.1.12
+
+
+
+ 0:2.4.0-150400.1.12
+
+
+
+
\ No newline at end of file
diff --git a/java/code/src/com/redhat/rhn/manager/rhnpackage/PackageManager.java b/java/code/src/com/redhat/rhn/manager/rhnpackage/PackageManager.java
index 84c0d5a3d6fb..f71f1fee3ad2 100644
--- a/java/code/src/com/redhat/rhn/manager/rhnpackage/PackageManager.java
+++ b/java/code/src/com/redhat/rhn/manager/rhnpackage/PackageManager.java
@@ -61,6 +61,7 @@
import com.redhat.rhn.manager.system.IncompatibleArchException;
import com.suse.manager.utils.PagedSqlQueryBuilder;
+import com.suse.oval.ShallowSystemPackage;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
@@ -322,6 +323,21 @@ public static DataResult systemPackageList(Long sid, PageContro
return PackageManager.getPackagesPerSystem(sid, "system_package_list", pc);
}
+ /**
+ * Returns list of packages for given server
+ *
+ * @param sid Server Id
+ * @return list of packages for given server
+ */
+ public static DataResult shallowSystemPackageList(Long sid) {
+ SelectMode m = ModeFactory.getMode("Package_queries", "shallow_system_package_list");
+
+ Map params = new HashMap<>();
+ params.put("sid", sid);
+
+ return m.execute(params);
+ }
+
/**
* Returns list of package for given server
* @param sid Server Id
diff --git a/java/code/src/com/redhat/rhn/taskomatic/task/CVEServerChannels.java b/java/code/src/com/redhat/rhn/taskomatic/task/CVEServerChannels.java
index cbb6f394b7cf..402e43db795f 100644
--- a/java/code/src/com/redhat/rhn/taskomatic/task/CVEServerChannels.java
+++ b/java/code/src/com/redhat/rhn/taskomatic/task/CVEServerChannels.java
@@ -14,7 +14,7 @@
*/
package com.redhat.rhn.taskomatic.task;
-import com.redhat.rhn.manager.audit.CVEAuditManager;
+import com.redhat.rhn.manager.audit.CVEAuditManagerOVAL;
import org.quartz.JobExecutionContext;
@@ -44,7 +44,7 @@ public void execute(JobExecutionContext context) {
// Measure time and calculate the total duration
Date start = new Date();
- CVEAuditManager.populateCVEChannels();
+ CVEAuditManagerOVAL.populateCVEChannels();
if (log.isDebugEnabled()) {
long duration = new Date().getTime() - start.getTime();
diff --git a/java/code/src/com/redhat/rhn/testing/ErrataTestUtils.java b/java/code/src/com/redhat/rhn/testing/ErrataTestUtils.java
index 58249c4edfc1..c1b3c91497af 100644
--- a/java/code/src/com/redhat/rhn/testing/ErrataTestUtils.java
+++ b/java/code/src/com/redhat/rhn/testing/ErrataTestUtils.java
@@ -12,8 +12,11 @@
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
+
package com.redhat.rhn.testing;
+import static com.redhat.rhn.domain.rhnpackage.test.PackageNameTest.createTestPackageName;
+
import com.redhat.rhn.common.db.datasource.ModeFactory;
import com.redhat.rhn.common.db.datasource.WriteMode;
import com.redhat.rhn.common.hibernate.HibernateFactory;
@@ -26,6 +29,7 @@
import com.redhat.rhn.domain.errata.ClonedErrata;
import com.redhat.rhn.domain.errata.Cve;
import com.redhat.rhn.domain.errata.Errata;
+import com.redhat.rhn.domain.errata.ErrataFactory;
import com.redhat.rhn.domain.errata.test.ErrataFactoryTest;
import com.redhat.rhn.domain.rhnpackage.Package;
import com.redhat.rhn.domain.rhnpackage.PackageEvr;
@@ -353,6 +357,78 @@ public static Package createTestPackage(User user, Errata errata, Channel channe
return result;
}
+ /**
+ * Create a {@link Package}.
+ *
+ * @param user the package owner
+ * @param errata an errata that will contain the new package
+ * @param channel the channel in which the new package is to be published
+ * @param arch the package architecture label
+ * @param name the package name
+ * @param epoch the package epoch in EVR
+ * @param version the package version in EVR
+ * @param release the package release in EVR
+ * @return the newly created patch
+ */
+ public static Package createTestPackage(User user, Errata errata, Channel channel, String arch, String name,
+ String epoch, String version, String release) {
+ Package result = createTestPackage(user, errata, channel, arch);
+
+ PackageEvr pevr =
+ PackageEvrFactory.lookupOrCreatePackageEvr(epoch, version, release, result.getPackageType());
+
+ result.setRpmVersion(result.getRpmVersion());
+ result.setDescription(result.getDescription());
+ result.setSummary(result.getSummary());
+ result.setPackageSize(result.getPackageSize());
+ result.setPayloadSize(result.getPayloadSize());
+ result.setBuildHost(result.getBuildHost());
+ result.setVendor(result.getVendor());
+ result.setPayloadFormat(result.getPayloadFormat());
+ result.setCompat(result.getCompat());
+ result.setPath(result.getPath());
+ result.setHeaderSignature(result.getHeaderSignature());
+ result.setCopyright(result.getCopyright());
+ result.setCookie(result.getCookie());
+ result.setPackageName(createTestPackageName(name));
+ result.setPackageEvr(pevr);
+ result.setPackageGroup(result.getPackageGroup());
+
+ TestUtils.saveAndFlush(result);
+
+ return result;
+ }
+
+ /**
+ * Create a {@link Package}.
+ *
+ * @param user the package owner
+ * @param channel the channel in which the new package is to be published
+ * @param arch the package architecture label
+ * @param name the package name
+ * @param epoch the package epoch in EVR
+ * @param version the package version in EVR
+ * @param release the package release in EVR
+ * @return the newly created patch
+ */
+ public static Package createTestPackage(User user, Channel channel, String arch, String name,
+ String epoch, String version, String release) {
+ return createTestPackage(user, null, channel, arch, name, epoch, version, release);
+ }
+
+ /**
+ * Create a {@link Package}.
+ *
+ * @param user the package owner
+ * @param channel the channel in which the new package is to be published
+ * @param arch the package architecture label
+ * @param name the package name
+ * @return the newly created patch
+ */
+ public static Package createTestPackage(User user, Channel channel, String arch, String name) {
+ return createTestPackage(user, null, channel, arch, name, "1", "0", "1");
+ }
+
/**
* Create a {@link Package} which has a greater version number than another.
* @param user the package owner
diff --git a/java/code/src/com/redhat/rhn/testing/TestCaseHelper.java b/java/code/src/com/redhat/rhn/testing/TestCaseHelper.java
index 5508e8623ebf..41052485d432 100644
--- a/java/code/src/com/redhat/rhn/testing/TestCaseHelper.java
+++ b/java/code/src/com/redhat/rhn/testing/TestCaseHelper.java
@@ -35,7 +35,7 @@ public static void tearDownHelper() {
if (HibernateFactory.inTransaction()) {
try {
HibernateFactory.rollbackTransaction();
- //HibernateFactory.commitTransaction();
+ // HibernateFactory.commitTransaction();
}
catch (TransactionException e) {
rollbackException = e;
diff --git a/java/code/src/com/suse/manager/reactor/messaging/RegisterMinionEventMessageAction.java b/java/code/src/com/suse/manager/reactor/messaging/RegisterMinionEventMessageAction.java
index 9f1b8ac14da2..9c34014c73a3 100644
--- a/java/code/src/com/suse/manager/reactor/messaging/RegisterMinionEventMessageAction.java
+++ b/java/code/src/com/suse/manager/reactor/messaging/RegisterMinionEventMessageAction.java
@@ -495,6 +495,7 @@ else if (!minion.getOrg().equals(org)) {
String kernelrelease = grains.getValueAsString("kernelrelease");
String osarch = grains.getValueAsString("osarch");
+ String cpe = grains.getValueAsString("cpe");
minion.setOs(osfullname);
minion.setOsFamily(osfamily);
@@ -507,6 +508,7 @@ else if (!minion.getOrg().equals(org)) {
minion.setModified(minion.getCreated());
minion.setContactMethod(getContactMethod(activationKey, isSaltSSH, minionId));
minion.setHostname(grains.getOptionalAsString(FQDN).orElse(null));
+ minion.setCpe(cpe);
systemInfo.getKernelLiveVersion().ifPresent(minion::setKernelLiveVersion);
diff --git a/java/code/src/com/suse/manager/utils/SaltUtils.java b/java/code/src/com/suse/manager/utils/SaltUtils.java
index 3318c7b1dd95..1c777b0319ab 100644
--- a/java/code/src/com/suse/manager/utils/SaltUtils.java
+++ b/java/code/src/com/suse/manager/utils/SaltUtils.java
@@ -1451,6 +1451,7 @@ else if ("debian".equalsIgnoreCase(grains.getValueAsString("os"))) {
server.setOsFamily(grains.getValueAsString("os_family"));
server.setRunningKernel(grains.getValueAsString("kernelrelease"));
server.setOs(grains.getValueAsString("osfullname"));
+ server.setCpe(grains.getValueAsString("cpe"));
/** Release is set directly from grain information for SUSE systems only.
RH systems require some parsing on the grains to get the correct release
diff --git a/java/code/src/com/suse/manager/webui/controllers/CVEAuditController.java b/java/code/src/com/suse/manager/webui/controllers/CVEAuditController.java
index a3a7d18b7382..0c42fb3d6946 100644
--- a/java/code/src/com/suse/manager/webui/controllers/CVEAuditController.java
+++ b/java/code/src/com/suse/manager/webui/controllers/CVEAuditController.java
@@ -25,7 +25,7 @@
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.manager.audit.CVEAuditImage;
-import com.redhat.rhn.manager.audit.CVEAuditManager;
+import com.redhat.rhn.manager.audit.CVEAuditManagerOVAL;
import com.redhat.rhn.manager.audit.CVEAuditServer;
import com.redhat.rhn.manager.audit.CVEAuditSystem;
import com.redhat.rhn.manager.audit.PatchStatus;
@@ -141,13 +141,13 @@ public static Object cveAudit(Request req, Response res, User user) {
switch (cveAuditRequest.getTarget()) {
case SERVER:
Set systemSet = RhnSetDecl.SYSTEMS.get(user).getElementValues();
- List cveAuditServers = CVEAuditManager
+ List cveAuditServers = CVEAuditManagerOVAL
.listSystemsByPatchStatus(user, cveAuditRequest.cveIdentifier,
cveAuditRequest.statuses);
cveAuditServers.forEach(serv -> serv.setSelected(systemSet.contains(serv.getId())));
return json(res, ResultJson.success(cveAuditServers));
case IMAGE:
- List cveAuditImages = CVEAuditManager
+ List cveAuditImages = CVEAuditManagerOVAL
.listImagesByPatchStatus(user, cveAuditRequest.cveIdentifier,
cveAuditRequest.statuses);
return json(res, ResultJson.success(cveAuditImages));
@@ -163,13 +163,13 @@ private static List handleRequest(CVEAuditRequest request, User
throws UnknownCVEIdentifierException {
switch (request.getTarget()) {
case SERVER:
- List cveAuditServers = CVEAuditManager
+ List cveAuditServers = CVEAuditManagerOVAL
.listSystemsByPatchStatus(user, request.cveIdentifier,
request.statuses);
return cveAuditServers.stream().map(x -> (CVEAuditSystem)x)
.collect(Collectors.toList());
case IMAGE:
- List cveAuditImages = CVEAuditManager
+ List cveAuditImages = CVEAuditManagerOVAL
.listImagesByPatchStatus(user, request.cveIdentifier,
request.statuses);
return cveAuditImages.stream().map(x -> (CVEAuditSystem)x)
diff --git a/java/code/src/com/suse/oval/OVALCachingFactory.java b/java/code/src/com/suse/oval/OVALCachingFactory.java
new file mode 100644
index 000000000000..206a1746afdf
--- /dev/null
+++ b/java/code/src/com/suse/oval/OVALCachingFactory.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2023 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;
+
+import com.redhat.rhn.common.db.datasource.CallableMode;
+import com.redhat.rhn.common.db.datasource.DataResult;
+import com.redhat.rhn.common.db.datasource.ModeFactory;
+import com.redhat.rhn.common.db.datasource.Row;
+import com.redhat.rhn.common.db.datasource.SelectMode;
+import com.redhat.rhn.common.hibernate.HibernateFactory;
+
+import com.redhat.rhn.domain.rhnpackage.PackageType;
+import com.redhat.rhn.manager.audit.CVEAffectedPackageItem;
+import com.suse.oval.manager.OVALLookupHelper;
+import com.suse.oval.ovaltypes.DefinitionType;
+import com.suse.oval.ovaltypes.OvalRootType;
+import com.suse.oval.vulnerablepkgextractor.ProductVulnerablePackages;
+import com.suse.oval.vulnerablepkgextractor.VulnerablePackage;
+import com.suse.oval.vulnerablepkgextractor.VulnerablePackagesExtractor;
+import com.suse.oval.vulnerablepkgextractor.VulnerablePackagesExtractors;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.Session;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class OVALCachingFactory extends HibernateFactory {
+ private static final Logger LOG = LogManager.getLogger(OVALCachingFactory.class);
+ private static OVALCachingFactory instance = new OVALCachingFactory();
+
+ private OVALCachingFactory() {
+ // Left empty on purpose
+ }
+
+ /**
+ * Extracts and save the list of vulnerable packages from {@code rootType}
+ *
+ * @param rootType the OVAL root to extract from
+ * */
+ // TODO: Following the single responsibility principle this method shouldn't have to extract vulnerable packages
+ // itself
+ public static void savePlatformsVulnerablePackages(OvalRootType rootType) {
+ CallableMode mode = ModeFactory.getCallableMode("oval_queries", "add_product_vulnerable_package");
+
+ OVALLookupHelper ovalLookupHelper = new OVALLookupHelper(rootType);
+
+ DataResult