-
Notifications
You must be signed in to change notification settings - Fork 46
Feat/rootio provider #914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
chait-slim
wants to merge
5
commits into
anchore:main
Choose a base branch
from
chait-slim:feat/rootio-provider
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Feat/rootio provider #914
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
5dcec5d
Add rootio provider for Rooi.io CVE feed
shakedembo d623637
Updated readme with root
shakedembo 12819d5
Fixed the linter issues by running `uv run ruff check --fix`
shakedembo 9efe013
feat: emit ROOTIO_UNAFFECTED markers for Root.io
chait-slim e3f28cf
update tests
chait-slim File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from dataclasses import dataclass, field | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from vunnel import provider, result, schema | ||
|
|
||
| from .parser import Parser | ||
|
|
||
| if TYPE_CHECKING: | ||
| import datetime | ||
|
|
||
|
|
||
| @dataclass | ||
| class Config: | ||
| runtime: provider.RuntimeConfig = field( | ||
| default_factory=lambda: provider.RuntimeConfig( | ||
| result_store=result.StoreStrategy.SQLITE, | ||
| existing_results=result.ResultStatePolicy.DELETE_BEFORE_WRITE, | ||
| ), | ||
| ) | ||
| request_timeout: int = 125 | ||
|
|
||
|
|
||
| class Provider(provider.Provider): | ||
| __schema__ = schema.OSSchema() | ||
| __distribution_version__ = int(__schema__.major_version) | ||
|
|
||
| _url = "https://api.root.io/external/cve_feed" | ||
|
|
||
| def __init__(self, root: str, config: Config | None = None): | ||
| if not config: | ||
| config = Config() | ||
| super().__init__(root, runtime_cfg=config.runtime) | ||
| self.config = config | ||
|
|
||
| self.parser = Parser( | ||
| workspace=self.workspace, | ||
| url=self._url, | ||
| download_timeout=self.config.request_timeout, | ||
| logger=self.logger, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def name(cls) -> str: | ||
| return "rootio" | ||
|
|
||
| def update(self, last_updated: datetime.datetime | None) -> tuple[list[str], int]: | ||
| count = 0 | ||
| with self.results_writer() as writer: | ||
| for namespace, vuln_id, record in self.parser.get(): | ||
| writer.write( | ||
| identifier=os.path.join(namespace, vuln_id.lower()), | ||
| schema=self.__schema__, | ||
| payload=record, | ||
| ) | ||
| count += 1 | ||
|
|
||
| return [self._url], count |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import copy | ||
| import logging | ||
| import os | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| import orjson | ||
|
|
||
| from vunnel.utils import http_wrapper as http | ||
| from vunnel.utils import vulnerability | ||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Generator | ||
|
|
||
| from vunnel import workspace | ||
|
|
||
|
|
||
| class Parser: | ||
| _data_dir = "rootio-data" | ||
| _data_filename = "cve_feed.json" | ||
|
|
||
| # Version format mapping for different distributions | ||
| # Add new OS support by adding entries here | ||
| _VERSION_FORMAT_MAP = { | ||
| "alpine": "apk", | ||
| "rhel": "rpm", | ||
| "centos": "rpm", | ||
| "rocky": "rpm", | ||
| "alma": "rpm", | ||
| "fedora": "rpm", | ||
| "suse": "rpm", | ||
| "opensuse": "rpm", | ||
| # Default is "dpkg" for debian, ubuntu, etc. | ||
| } | ||
|
|
||
| def __init__( | ||
| self, | ||
| workspace: workspace.Workspace, | ||
| url: str, | ||
| download_timeout: int = 125, | ||
| logger: logging.Logger | None = None, | ||
| ): | ||
| self.download_timeout = download_timeout | ||
| self.data_dir_path = Path(workspace.input_path) / self._data_dir | ||
| self.url = url | ||
|
|
||
| if not logger: | ||
| logger = logging.getLogger(self.__class__.__name__) | ||
| self.logger = logger | ||
|
|
||
| def _download(self) -> None: | ||
| if not os.path.exists(self.data_dir_path): | ||
| os.makedirs(self.data_dir_path, exist_ok=True) | ||
|
|
||
| try: | ||
| self.logger.info(f"downloading Root.io CVE feed from {self.url}") | ||
| r = http.get(self.url, self.logger, stream=True, timeout=self.download_timeout) | ||
| file_path = self.data_dir_path / self._data_filename | ||
| with open(file_path, "wb") as fp: | ||
| for chunk in r.iter_content(): | ||
| fp.write(chunk) | ||
| except Exception: | ||
| self.logger.exception(f"Error downloading Root.io data from {self.url}") | ||
| raise | ||
|
|
||
| def _normalize(self, distro_name: str, distro_data: dict[str, Any]) -> dict[str, dict[str, Any]]: | ||
| """Transform Root.io data into OS schema format with unaffected package indicators""" | ||
| vuln_dict = {} | ||
|
|
||
| distro_version = distro_data.get("distroversion", "unknown") | ||
| namespace = f"rootio:distro:{distro_name}:{distro_version}" | ||
|
|
||
| for package_data in distro_data.get("packages", []): | ||
| pkg_info = package_data.get("pkg", {}) | ||
| package_name = pkg_info.get("name", "") | ||
|
|
||
| for cve_id, cve_info in pkg_info.get("cves", {}).items(): | ||
| if cve_id not in vuln_dict: | ||
| record = copy.deepcopy(vulnerability.vulnerability_element) | ||
| record["Vulnerability"]["Name"] = cve_id | ||
| record["Vulnerability"]["NamespaceName"] = namespace | ||
|
|
||
| # Build reference links | ||
| reference_links = vulnerability.build_reference_links(cve_id) | ||
| record["Vulnerability"]["Link"] = reference_links[0] if reference_links else "" | ||
|
|
||
| record["Vulnerability"]["Severity"] = "Unknown" | ||
| record["Vulnerability"]["Description"] = f"Vulnerability {cve_id} in {package_name}" | ||
| record["Vulnerability"]["FixedIn"] = [] | ||
| record["Vulnerability"]["Metadata"] = { | ||
| "CVE": [{"Name": cve_id, "Link": reference_links[0] if reference_links else ""}], | ||
| } | ||
| vuln_dict[cve_id] = record | ||
|
|
||
| # Add fixed version info | ||
| cve_record = vuln_dict[cve_id] | ||
| fixed_versions = cve_info.get("fixed_versions", []) | ||
|
|
||
| # Determine version format based on distro | ||
| version_format = self._get_version_format(distro_name) | ||
|
|
||
| # Add the fixed versions | ||
| # Note: Root.io uses different suffixes for different distros: | ||
| # - Debian/Ubuntu: .root.io suffix (e.g., 1.5.2-6+deb12u1.root.io.4) | ||
| # - Alpine: -rXX007X suffix (e.g., -r00071, -r10074) | ||
| for fixed_version in fixed_versions: | ||
| cve_record["Vulnerability"]["FixedIn"].append({ | ||
| "Name": package_name, | ||
| "Version": fixed_version, | ||
| "VersionFormat": version_format, | ||
| "NamespaceName": namespace, | ||
| "VendorAdvisory": {"NoAdvisory": True}, | ||
| }) | ||
|
|
||
| # If no fixed versions, add unfixed entry | ||
| if not fixed_versions: | ||
| cve_record["Vulnerability"]["FixedIn"].append({ | ||
| "Name": package_name, | ||
| "Version": "", # Empty version indicates no fix available | ||
| "VersionFormat": version_format, | ||
| "NamespaceName": namespace, | ||
| "VendorAdvisory": {"NoAdvisory": True}, | ||
| }) | ||
|
|
||
| return vuln_dict | ||
|
|
||
| def _get_version_format(self, distro_name: str) -> str: | ||
| """Map distro name to version format.""" | ||
| return self._VERSION_FORMAT_MAP.get(distro_name, "dpkg") | ||
|
|
||
| def get(self) -> Generator[tuple[str, str, dict[str, Any]], None, None]: | ||
| """Download, parse and yield Root.io vulnerability records""" | ||
| # Download the data | ||
| self._download() | ||
|
|
||
| # Load the JSON data | ||
| with open(self.data_dir_path / self._data_filename) as fh: | ||
| feed_data = orjson.loads(fh.read()) | ||
|
|
||
| # Process each distribution | ||
| for distro_name, distro_list in feed_data.items(): | ||
| for distro_data in distro_list: | ||
| distro_version = distro_data.get("distroversion", "unknown") | ||
| namespace = f"rootio:distro:{distro_name}:{distro_version}" | ||
|
|
||
| vuln_records = self._normalize(distro_name, distro_data) | ||
|
|
||
| for vuln_id, record in vuln_records.items(): | ||
| yield namespace, vuln_id, record | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Root.io provider tests |
66 changes: 66 additions & 0 deletions
66
tests/unit/providers/rootio/test-fixtures/sample_feed.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| { | ||
| "alpine": [ | ||
| { | ||
| "distroversion": "3.17", | ||
| "packages": [ | ||
| { | ||
| "pkg": { | ||
| "name": "libssl3", | ||
| "cves": { | ||
| "CVE-2023-0464": { | ||
| "fixed_versions": ["3.0.8-r4"] | ||
| }, | ||
| "CVE-2023-0465": { | ||
| "fixed_versions": ["3.0.8-r4"] | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "pkg": { | ||
| "name": "openssl", | ||
| "cves": { | ||
| "CVE-2023-0464": { | ||
| "fixed_versions": ["3.0.8-r4"] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "debian": [ | ||
| { | ||
| "distroversion": "11", | ||
| "packages": [ | ||
| { | ||
| "pkg": { | ||
| "name": "libgcrypt20", | ||
| "cves": { | ||
| "CVE-2021-40528": { | ||
| "fixed_versions": ["1.8.7-6+deb11u1"] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "ubuntu": [ | ||
| { | ||
| "distroversion": "22.04", | ||
| "packages": [ | ||
| { | ||
| "pkg": { | ||
| "name": "python3-pip", | ||
| "cves": { | ||
| "CVE-2023-5752": { | ||
| "fixed_versions": ["22.0.2+dfsg-1ubuntu0.4"] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we had discussed at #863 (comment) that this vulnerability feed only supports records of fixes? Should this code ever be reachable?