Skip to content

Commit

Permalink
cli: change vulnerability applied fixes message
Browse files Browse the repository at this point in the history
Instead of showing the count of applied fixes per package,
we are now switching to count be vulnerability instead. The
main reason is to make the count consistent with the other
summary information we have on pro vulnerability list
  • Loading branch information
lucasmoura committed Oct 10, 2024
1 parent cd3f529 commit b4c25eb
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 42 deletions.
34 changes: 34 additions & 0 deletions features/cli/vulnerability_list.feature
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,37 @@ Feature: CLI vulnerability list command
Examples: ubuntu
| release | machine_type |
| xenial | lxd-container |

@slow
Scenario Outline: CLI vulnerability list after upgrade
Given a `<release>` `<machine_type>` machine with ubuntu-advantage-tools installed
When I push static file `security_issues_xenial.json` to machine
And I run `pro vulnerability list --data-file=/tmp/security_issues_xenial.json` as non-root
And I remove colors from output
And I remove links from output
Then stdout contains substring:
"""
Vulnerabilities with applied fixes:
861 applied via Ubuntu Security (32 high, 494 medium, 322 low, 13 negligible)
Vulnerabilities with fixes available:
461 fixable via Ubuntu Pro (10 high, 293 medium, 136 low, 22 negligible)
1 fixable via Ubuntu Security (1 medium)
"""
When I attach `contract_token` with sudo
And I apt upgrade
When I run `pro vulnerability list --data-file=/tmp/security_issues_xenial.json` as non-root
And I remove colors from output
And I remove links from output
Then stdout contains substring:
"""
No CVEs found that affects this system
Vulnerabilities with applied fixes:
461 applied via Ubuntu Pro (10 high, 293 medium, 136 low, 22 negligible)
862 applied via Ubuntu Security (32 high, 495 medium, 322 low, 13 negligible)
"""

Examples: ubuntu
| release | machine_type |
| xenial | lxd-container |
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from uaclient.api.u.pro.security.vulnerabilities._common.v1 import (
ProManifestSourcePackage,
SourcePackages,
VulnerabilitiesAlreadyFixed,
VulnerabilityParser,
VulnerabilityStatus,
_get_source_package_from_vulnerabilities_data,
Expand Down Expand Up @@ -414,3 +415,58 @@ def test_parse_manifest_file(
vulnerabilities_data=vulnerabilities_data,
).get()
)


class TestVulnerabilitiesAlreadyFixed:

@pytest.mark.parametrize(
"vulnerabilities,expected_result",
(
(
[
("CVE-1234-5", "ubuntu_pro", "high"),
("CVE-1234-5", "ubuntu_security", "high"),
("CVE-1552-5", "ubuntu_pro", "medium"),
("CVE-1382-5", "ubuntu_security", "low"),
],
{
"count": {
"ubuntu_pro": 2,
"ubuntu_security": 2,
},
"info": {
"ubuntu_pro": {"high": 1, "medium": 1},
"ubuntu_security": {"high": 1, "low": 1},
},
},
),
(
[
("USN-1234-5", "ubuntu_pro"),
("USN-1234-5", "ubuntu_security"),
("USN-1552-5", "ubuntu_pro"),
("USN-1382-5", "ubuntu_security"),
],
{
"count": {
"ubuntu_pro": 2,
"ubuntu_security": 2,
},
"info": {
"ubuntu_pro": {},
"ubuntu_security": {},
},
},
),
),
)
def test_vulnerabilities_already_fixed_to_dict(
self,
vulnerabilities,
expected_result,
):
vulnerabilities_already_fixed = VulnerabilitiesAlreadyFixed()
for vulnerability in vulnerabilities:
vulnerabilities_already_fixed.add_vulnerability(*vulnerability)

assert expected_result == vulnerabilities_already_fixed.to_dict()
85 changes: 47 additions & 38 deletions uaclient/api/u/pro/security/vulnerabilities/_common/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,47 @@ def _get_vulnerability_fix_status(
)


class VulnerabilitiesAlreadyFixed:
def __init__(self):
self.vulns = {}

def add_vulnerability(
self,
vuln_name: str,
vuln_pocket: str,
vuln_priority: Optional[str] = None,
):
if vuln_pocket not in self.vulns:
self.vulns[vuln_pocket] = {
"issues": set(),
"priority_count": {},
}

if vuln_name not in self.vulns[vuln_pocket]["issues"]:
self.vulns[vuln_pocket]["issues"].add(vuln_name)

if vuln_priority:
if vuln_priority in self.vulns[vuln_pocket]["priority_count"]:
self.vulns[vuln_pocket]["priority_count"][
vuln_priority
] += 1
else:
self.vulns[vuln_pocket]["priority_count"][
vuln_priority
] = 1

def to_dict(self):
dict_repr = {
"count": {},
"info": {},
} # type: Dict[str, Dict[str, Any]]
for pocket, info in self.vulns.items():
dict_repr["count"][pocket] = len(info["issues"])
dict_repr["info"][pocket] = info["priority_count"]

return dict_repr


class VulnerabilityParser(metaclass=abc.ABCMeta):
vulnerability_type = None # type: str

Expand Down Expand Up @@ -510,39 +551,13 @@ def _list_binary_packages(self, installed_pkgs_by_source: Dict[str, Any]):
) in sorted(binary_pkgs.items()):
yield source_pkg, binary_pkg_name, binary_installed_version

def _add_info_to_fixed_vulnerabilities_count(
self,
pocket: str,
fixed_vulnerability_info: Dict[str, Any],
vulnerability_info: Dict[str, Any],
) -> Dict[str, Any]:
ubuntu_priority = vulnerability_info.get("ubuntu_priority")

if not ubuntu_priority:
return fixed_vulnerability_info

if pocket not in fixed_vulnerability_info:
fixed_vulnerability_info[pocket] = {ubuntu_priority: 1}
elif ubuntu_priority not in fixed_vulnerability_info[pocket]:
fixed_vulnerability_info[pocket][ubuntu_priority] = 1
else:
fixed_vulnerability_info[pocket][ubuntu_priority] += 1

return fixed_vulnerability_info

def get_vulnerabilities_for_installed_pkgs(
self,
vulnerabilities_data: Dict[str, Any],
installed_pkgs_by_source: Dict[str, Dict[str, str]],
):
vulnerabilities = {} # type: Dict[str, Any]
applied_fixes_count = {
"count": {
"ubuntu_security": 0,
"ubuntu_pro": 0,
},
"info": {},
}
vulnerabilities_already_fixed = VulnerabilitiesAlreadyFixed()

affected_pkgs = vulnerabilities_data.get("packages", {})
vulns_info = vulnerabilities_data.get("security_issues", {}).get(
Expand Down Expand Up @@ -651,16 +666,10 @@ def get_vulnerabilities_for_installed_pkgs(
if pocket in ("release", "updates", "security")
else "ubuntu_pro"
)
applied_fixes_count["count"][
ubuntu_pocket_translation
] += 1

applied_fixes_count["info"] = (
self._add_info_to_fixed_vulnerabilities_count(
ubuntu_pocket_translation,
applied_fixes_count["info"],
vuln_info,
)
vulnerabilities_already_fixed.add_vulnerability(
vuln_name=vuln_name,
vuln_pocket=ubuntu_pocket_translation,
vuln_priority=vuln_info.get("ubuntu_priority"),
)

continue
Expand All @@ -671,7 +680,7 @@ def get_vulnerabilities_for_installed_pkgs(
),
vulnerabilities_info={
"vulnerabilities": vulnerabilities,
"applied_fixes_count": applied_fixes_count,
"applied_fixes_count": vulnerabilities_already_fixed.to_dict(),
},
)

Expand Down
8 changes: 4 additions & 4 deletions uaclient/cli/vulnerability/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def _create_fixable_cves_count(vulnerabilities) -> str:
vulnerabilities
)

for pocket, pocket_info in fixable_vulnerabilities_info.items():
for pocket, pocket_info in sorted(fixable_vulnerabilities_info.items()):
if not pocket_info["count"]:
continue

Expand All @@ -227,7 +227,7 @@ def _create_fixable_usns_count(vulnerabilities) -> str:
fixable_vulnerabilities_info = _get_info_from_vulnerabilities(
vulnerabilities
)
for pocket, pocket_info in fixable_vulnerabilities_info.items():
for pocket, pocket_info in sorted(fixable_vulnerabilities_info.items()):
if not pocket_info["count"]:
continue

Expand Down Expand Up @@ -282,7 +282,7 @@ def _create_unfixable_usns_count(vulnerabilities) -> str:
def _create_already_fixed_cves_count(applied_fixes_count) -> str:
if any(count for pocket, count in applied_fixes_count["count"].items()):
content = []
for pocket, count in applied_fixes_count["count"].items():
for pocket, count in sorted(applied_fixes_count["count"].items()):
if not count:
continue

Expand Down Expand Up @@ -310,7 +310,7 @@ def _create_already_fixed_cves_count(applied_fixes_count) -> str:
def _create_already_fixed_usns_count(applied_fixes_count) -> str:
if any(count for pocket, count in applied_fixes_count["count"].items()):
content = []
for pocket, count in applied_fixes_count["count"].items():
for pocket, count in sorted(applied_fixes_count["count"].items()):
if not count:
continue

Expand Down

0 comments on commit b4c25eb

Please sign in to comment.