diff --git a/features/cli/vulnerability_list.feature b/features/cli/vulnerability_list.feature index 64ce0c0a85..6c165a681d 100644 --- a/features/cli/vulnerability_list.feature +++ b/features/cli/vulnerability_list.feature @@ -41,7 +41,7 @@ Feature: CLI vulnerability list command 13 applied via Ubuntu Security (2 high, 6 medium, 5 low) Vulnerabilities with fixes available: - 10 vulnerabilities found (3 high, 4 medium, 2 low, 1 negligible) + 10 fixable via Ubuntu Pro (3 high, 4 medium, 2 low, 1 negligible) """ When I run `pro vulnerability list --all --data-file=/tmp/security_issues_xenial --manifest-file=/tmp/manifest` as non-root And I remove colors from output @@ -73,7 +73,7 @@ Feature: CLI vulnerability list command 13 applied via Ubuntu Security (2 high, 6 medium, 5 low) Vulnerabilities with fixes available: - 10 vulnerabilities found (3 high, 4 medium, 2 low, 1 negligible) + 10 fixable via Ubuntu Pro (3 high, 4 medium, 2 low, 1 negligible) Vulnerabilities with no fixes available: 9 unfixable vulnerabilities found (7 medium, 2 low) @@ -116,7 +116,7 @@ Feature: CLI vulnerability list command 1 applied via Ubuntu Security Vulnerabilities with fixes available: - 5 vulnerabilities found + 5 fixable via Ubuntu Pro """ When I create the file `/tmp/manifest` with the following: """ diff --git a/uaclient/cli/vulnerability/list.py b/uaclient/cli/vulnerability/list.py index 7a0f43910b..3631650b50 100644 --- a/uaclient/cli/vulnerability/list.py +++ b/uaclient/cli/vulnerability/list.py @@ -1,4 +1,5 @@ -from typing import Dict, NamedTuple +import re +from typing import Any, Dict # noqa: F401 from uaclient import config, exceptions, messages from uaclient.api.u.pro.security.vulnerabilities.cve.v1 import ( @@ -17,11 +18,6 @@ from uaclient.cli.formatter import Table from uaclient.cli.vulnerability import util as vuln_util -VulnerabilityCountInfo = NamedTuple( - "VulnerabilityCountInfo", - [("count", int), ("count_by_priority", Dict[str, int])], -) - def _create_usn_table(usns, num_rows=None): usns_sorted_by_name = sorted(usns, key=lambda usn: usn.name) @@ -91,12 +87,12 @@ def _create_cve_table(cves, num_rows=None): ) -def _get_info_from_vulnerabilities(vulnerabilities, fixable_vuln: bool): +def _get_unfixable_info_from_vulnerabilities(vulnerabilities): count = 0 vulnerability_count_info = {} # type: Dict[str, int] for vuln in vulnerabilities: - if (vuln.fixable == "yes") is fixable_vuln: + if vuln.fixable != "yes": count += 1 if not getattr(vuln, "ubuntu_priority", None): @@ -107,20 +103,59 @@ def _get_info_from_vulnerabilities(vulnerabilities, fixable_vuln: bool): else: vulnerability_count_info[vuln.ubuntu_priority] = 1 - return VulnerabilityCountInfo( - count=count, - count_by_priority=vulnerability_count_info, - ) + return (count, vulnerability_count_info) + + +def _get_info_from_vulnerabilities(vulnerabilities): + vulnerability_count_info = { + "ubuntu_security": {"count": 0, "info": {}}, + "ubuntu_pro": { + "count": 0, + "info": {}, + }, + } # type: Dict[str, Dict[str, Any]] + + for vuln in vulnerabilities: + if vuln.fixable == "yes": + pocket = ( + "ubuntu_pro" + if any( + pkg + for pkg in vuln.affected_packages + if re.match( + r"^(esm|fips)", pkg.fix_available_from or "no-fix" + ) + ) + else "ubuntu_security" + ) + vulnerability_count_info[pocket]["count"] += 1 + if not getattr(vuln, "ubuntu_priority", None): + continue + + if ( + vuln.ubuntu_priority + in vulnerability_count_info[pocket]["info"] + ): + vulnerability_count_info[pocket]["info"][ + vuln.ubuntu_priority + ] += 1 + else: + vulnerability_count_info[pocket]["info"][ + vuln.ubuntu_priority + ] = 1 -def _get_fixable_color_count(fixable_vulnerabilities_info): - if "critical" in fixable_vulnerabilities_info.count_by_priority: + return vulnerability_count_info + + +def _get_fixable_color_count(pocket_info): + if "critical" in pocket_info["info"]: main_color = messages.TxtColor.FAIL - elif "high" in fixable_vulnerabilities_info.count_by_priority: + elif "high" in pocket_info["info"]: main_color = messages.TxtColor.ORANGE - elif "medium" in fixable_vulnerabilities_info.count_by_priority: + elif "medium" in pocket_info["info"]: main_color = messages.TxtColor.WARNINGYELLOW - elif "low" in fixable_vulnerabilities_info.count_by_priority: + elif "low" in pocket_info["info"]: main_color = messages.TxtColor.INFOBLUE else: main_color = "" @@ -128,11 +163,11 @@ def _get_fixable_color_count(fixable_vulnerabilities_info): if main_color: return "{}{}{}".format( main_color, - str(fixable_vulnerabilities_info.count), + str(pocket_info["count"]), messages.TxtColor.ENDC, ) - return str(fixable_vulnerabilities_info.count) + return str(pocket_info["count"]) def _get_count_msg_by_priority(count_by_priority): @@ -156,21 +191,26 @@ def _get_count_msg_by_priority(count_by_priority): def _create_fixable_cves_count(vulnerabilities) -> str: msg = messages.CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_HEADER + "\n" fixable_vulnerabilities_info = _get_info_from_vulnerabilities( - vulnerabilities, fixable_vuln=True + vulnerabilities ) - count_with_color = _get_fixable_color_count(fixable_vulnerabilities_info) - msg += ( - " " - + count_with_color - + " " - + messages.CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_COUNT - + " (" - + _get_count_msg_by_priority( - fixable_vulnerabilities_info.count_by_priority + + for pocket, pocket_info in fixable_vulnerabilities_info.items(): + if not pocket_info["count"]: + continue + + count_with_color = _get_fixable_color_count(pocket_info) + msg += ( + " " + + count_with_color + + " " + + messages.CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_COUNT.format( + pocket=pocket.title().replace("_", " ") + ) + + " (" + + _get_count_msg_by_priority(pocket_info["info"]) + + ")" + + "\n" ) - + ")" - + "\n" - ) return msg @@ -178,32 +218,37 @@ def _create_fixable_cves_count(vulnerabilities) -> str: def _create_fixable_usns_count(vulnerabilities): msg = messages.CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_HEADER + "\n" fixable_vulnerabilities_info = _get_info_from_vulnerabilities( - vulnerabilities, fixable_vuln=True - ) - msg += ( - " " - + str(fixable_vulnerabilities_info.count) - + " " - + messages.CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_COUNT - + "\n" + vulnerabilities ) + for pocket, pocket_info in fixable_vulnerabilities_info.items(): + if not pocket_info["count"]: + continue + + msg += ( + " " + + str(pocket_info["count"]) + + " " + + messages.CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_COUNT.format( + pocket=pocket.title().replace("_", " ") + ) + + "\n" + ) + return msg def _create_unfixable_cves_count(vulnerabilities) -> str: msg = messages.CLI_VULNERABILITY_LIST_UNFIXABLE_AVAILABLE_HEADER + "\n" - unfixable_vulnerabilities_info = _get_info_from_vulnerabilities( - vulnerabilities, fixable_vuln=False + count, unfixable_vulnerabilities_info = ( + _get_unfixable_info_from_vulnerabilities(vulnerabilities) # noqa ) msg += ( " " - + str(unfixable_vulnerabilities_info.count) + + str(count) + " " + messages.CLI_VULNERABILITY_LIST_UNFIXABLE_AVAILABLE_COUNT + " (" - + _get_count_msg_by_priority( - unfixable_vulnerabilities_info.count_by_priority - ) + + _get_count_msg_by_priority(unfixable_vulnerabilities_info) + ")" + "\n" ) @@ -213,12 +258,12 @@ def _create_unfixable_cves_count(vulnerabilities) -> str: def _create_unfixable_usns_count(vulnerabilities): msg = messages.CLI_VULNERABILITY_LIST_UNFIXABLE_AVAILABLE_HEADER + "\n" - unfixable_vulnerabilities_info = _get_info_from_vulnerabilities( - vulnerabilities, fixable_vuln=False + count, unfixable_vulnerabilities_info = ( + _get_unfixable_info_from_vulnerabilities(vulnerabilities) # noqa ) msg += ( " " - + str(unfixable_vulnerabilities_info.count) + + str(count) + " " + messages.CLI_VULNERABILITY_LIST_UNFIXABLE_AVAILABLE_COUNT + "\n" diff --git a/uaclient/messages/__init__.py b/uaclient/messages/__init__.py index c0268fbb44..876dc69146 100644 --- a/uaclient/messages/__init__.py +++ b/uaclient/messages/__init__.py @@ -1343,7 +1343,7 @@ class TxtColor: + TxtColor.ENDC ) CLI_VULNERABILITY_LIST_FIXES_AVAILABLE_COUNT = t.gettext( - "vulnerabilities found" + "fixable via {pocket}" ) CLI_VULNERABILITY_LIST_UNFIXABLE_AVAILABLE_COUNT = t.gettext( "unfixable vulnerabilities found"