Skip to content
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

make redhat conversion tool downloadable with Pip #278

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added tools/__init__.py
Empty file.
1 change: 1 addition & 0 deletions tools/redhat/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
redhat_osv.egg-info/
11 changes: 6 additions & 5 deletions tools/redhat/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
jsonschema = "*"
requests = "*"

[dev-packages]
setuptools = "*"
pylint = "*"
yapf = "*"
yapf = "*"
redhat_osv = {path = "."}

[packages]
redhat_osv = {editable = true, path = "."}
303 changes: 294 additions & 9 deletions tools/redhat/Pipfile.lock

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions tools/redhat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@

## Setup

This tool is installable via Pip using a git reference such as:

~~~
redhat-osv @ git+https://github.com/ossf/osv-schema@0cef5d4#egg=redhat_osv&subdirectory=tools/redhat
~~~

Dependency management is therefore split between `setup.py` and `Pipenv`. Runtime dependencies are declared in
`setup.py` and installable by installing the project into a pipenv environment:

~~~
$ pipenv sync
$ pipenv shell
$ pipenv install -e .
~~~

## Usage

Needs to be run in a folder where the Red Hat CSAF documents to convert already exist. Files can be downloaded the [Red Hat Customer Portal Security Data section](https://access.redhat.com/security/data/csaf/v2/advisories/)
~~~
$ ./convert_redhat.py csaf/rhsa-2024_4546.json
$ pipenv run convert_redhat testdata/rhsa-2024_4546.json
~~~

OSV documents will be output in the `osv` directory by default. Override the default with the `--output_directory` option.

## Tests
## Running Tests

Run the tests like so:

~~~
$ python3 -m unittest *_test.py
$ pipenv run python3 -m unittest redhat_osv/*_test.py
~~~
Empty file added tools/redhat/__init__.py
Empty file.
40 changes: 1 addition & 39 deletions tools/redhat/convert_redhat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,7 @@

import requests
from jsonschema import validate
from csaf import CSAF
from osv import DATE_FORMAT, OSV, OSVEncoder, SCHEMA_VERSION


class RedHatConverter:
"""
Class which converts and validates a CSAF string to an OSV string
"""
SCHEMA = (
f"https://raw.githubusercontent.com/ossf/osv-schema/v{SCHEMA_VERSION}"
"/validation/schema.json")
REQUEST_TIMEOUT = 60

def __init__(self):
schema_content = requests.get(self.SCHEMA, timeout=self.REQUEST_TIMEOUT)
self.osv_schema = schema_content.json()

def convert(self,
csaf_content: str,
modified: str,
published: str = "") -> tuple[str, str]:
"""
Converts csaf_content json string into an OSV json string
returns an OSV ID and the json string content of the OSV file
the json string content will be empty if no content is applicable
throws a validation error in the schema doesn't validate correctly.
The modified value for osv is passed in so it matches what's in all.json
Raises ValueError is CSAF file can't be parsed
"""
csaf = CSAF(csaf_content)
osv = OSV(csaf, modified, published)

# We convert from an OSV object to a JSON string here in order to use the OSVEncoder
# Once we OSV json string data we validate it using the OSV schema
osv_content = json.dumps(osv, cls=OSVEncoder, indent=2)
osv_data = json.loads(osv_content)
validate(osv_data, schema=self.osv_schema)

return osv.id, osv_content
from redhat_osv.osv import DATE_FORMAT, RedHatConverter


def main():
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import unittest
from datetime import datetime
import json
from convert_redhat import RedHatConverter
from osv import DATE_FORMAT
from redhat_osv.osv import DATE_FORMAT, RedHatConverter


class TestRedHatConverter(unittest.TestCase):
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Test parsing CSAF v2 advisories"""
import unittest

from csaf import Remediation
from redhat_osv.csaf import Remediation


class CSAFTest(unittest.TestCase):
Expand Down
58 changes: 49 additions & 9 deletions tools/redhat/osv.py → tools/redhat/redhat_osv/osv.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Module for parsing converting CSAF to OSV data"""
import re
from dataclasses import field, dataclass, InitVar
from json import JSONEncoder
import json
from typing import Literal

from csaf import Remediation, CSAF
import requests
from jsonschema import validate
from redhat_osv.csaf import Remediation, CSAF

# Update this if verified against a later version
SCHEMA_VERSION = "1.6.5"
Expand All @@ -23,7 +24,7 @@
)


class OSVEncoder(JSONEncoder):
class OSVEncoder(json.JSONEncoder):
"""Encodes OSV objects into JSON format"""

def default(self, o):
Expand Down Expand Up @@ -175,19 +176,22 @@ def _convert_references(self, csaf) -> list[dict[str, str]]:
REDHAT_ADVISORY_URL)
references[reference["url"]] = "ADVISORY"
else:
references[reference["url"]] = self._get_reference_type_and_add_go_related(
reference)
references[reference[
"url"]] = self._get_reference_type_and_add_go_related(
reference)
for vulnerability in csaf.vulnerabilities:
for reference in vulnerability.references:
# This captures the CVE specific information
if reference["category"] == "self":
references[reference["url"]] = "REPORT"
else:
references[reference["url"]] = self._get_reference_type_and_add_go_related(
reference)
references[reference[
"url"]] = self._get_reference_type_and_add_go_related(
reference)
return [{"type": t, "url": u} for u, t in references.items()]

def _get_reference_type_and_add_go_related(self, reference: dict[str, str]) -> str:
def _get_reference_type_and_add_go_related(
self, reference: dict[str, str]) -> str:
"""
Convert references from CSAF into typed referenced in OSV
Also make sure to add a related entry for any GO advisory references found
Expand All @@ -201,3 +205,39 @@ def _get_reference_type_and_add_go_related(self, reference: dict[str, str]) -> s
return "REPORT"
return "ARTICLE"


class RedHatConverter:
"""
Class which converts and validates a CSAF string to an OSV string
"""
SCHEMA = (
f"https://raw.githubusercontent.com/ossf/osv-schema/v{SCHEMA_VERSION}"
"/validation/schema.json")
REQUEST_TIMEOUT = 60

def __init__(self):
schema_content = requests.get(self.SCHEMA, timeout=self.REQUEST_TIMEOUT)
self.osv_schema = schema_content.json()

def convert(self,
csaf_content: str,
modified: str,
published: str = "") -> tuple[str, str]:
"""
Converts csaf_content json string into an OSV json string
returns an OSV ID and the json string content of the OSV file
the json string content will be empty if no content is applicable
throws a validation error in the schema doesn't validate correctly.
The modified value for osv is passed in so it matches what's in all.json
Raises ValueError is CSAF file can't be parsed
"""
csaf = CSAF(csaf_content)
osv = OSV(csaf, modified, published)

# We convert from an OSV object to a JSON string here in order to use the OSVEncoder
# Once we OSV json string data we validate it using the OSV schema
osv_content = json.dumps(osv, cls=OSVEncoder, indent=2)
osv_data = json.loads(osv_content)
validate(osv_data, schema=self.osv_schema)

return osv.id, osv_content
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Test Intermediate OSV object creation"""
import unittest

from csaf import CSAF
from osv import OSV, Event
from redhat_osv.csaf import CSAF
from redhat_osv.osv import OSV, Event


class ScoreTest(unittest.TestCase):
Expand Down
26 changes: 26 additions & 0 deletions tools/redhat/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
""" Convert a CSAF document to OSV format
i.e. https://access.redhat.com/security/data/csaf/v2/advisories/2024/rhsa-2024_4546.json
"""

from setuptools import setup, find_packages

REQUIRES = [
"jsonschema",
"requests"
]

setup(
name="redhat_osv",
version="1.0.0",
description="Convert Red Hat CSAF documents to OSV format",
author_email="[email protected]",
url="",
keywords=["OSV", "CSAF"],
install_requires=REQUIRES,
classifiers=[
"Programming Language :: Python :: 3",
],
packages=["redhat_osv"],
entry_points={"console_scripts": ["convert_redhat=convert_redhat:main"]},
long_description="The purpose of this tool is to convert from Red Hat CSAF documents to OSV",
)