-
Notifications
You must be signed in to change notification settings - Fork 4
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
Plausibility test bom refs #14
Closed
Closed
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
1433042
check for plausibility of bom-refs
CBeck-96 32d83d0
main restored
CBeck-96 46d60ab
Merge branch 'Festo-se:main' into main
CBeck-96 4f2d9af
Merge branch 'Festo-se:main' into main
CBeck-96 9df8f18
plausibility test
CBeck-96 84a9c65
added test for uniquness of bomrefs, test for tree connectivity removed
CBeck-96 ace7944
check for non unique bom-refs
CBeck-96 b9501e1
migrated non unique bom-ref to validate
CBeck-96 3191238
plausibility check integrated into validate
CBeck-96 9f40972
plausability check deleted
CBeck-96 c252f4f
tried solving isort errors
CBeck-96 5c5aa9c
isorted helper
CBeck-96 934e344
moved logging of validate to main
CBeck-96 ac27a1e
run isort then black
CBeck-96 28dc0fe
debugged test validate
CBeck-96 73ded04
another bug in test validate removed
CBeck-96 7f7a502
plausibility is a flag
CBeck-96 63f1378
used global logger
CBeck-96 203a9bc
Merge branch 'Festo-se:main' into plausibility_test_bom_refs
CBeck-96 2b660da
grammar and error
CBeck-96 1dce2a5
removed test sbom
CBeck-96 3b53d0d
Merge branch 'Festo-se:main' into plausibility_test_bom_refs
CBeck-96 265575a
Merge branch 'main' into plausibility_test_bom_refs
CBeck-96 45ebdf5
Merge branch 'main' into plausibility_test_bom_refs
italvi db01b63
Merge branch 'main' into plausibility_test_bom_refs
italvi 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 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 |
---|---|---|
|
@@ -14,10 +14,11 @@ | |
from cdxev.auxiliary.output import write_sbom | ||
from cdxev.build_public_bom import build_public_bom | ||
from cdxev.error import AppError, InputFileError | ||
from cdxev.log import configure_logging | ||
from cdxev.log import LogMessage, configure_logging | ||
from cdxev.merge import merge | ||
from cdxev.merge_vex import merge_vex | ||
from cdxev.validator import validate_sbom | ||
from cdxev.validator.warningsngreport import WarningsNgReporter | ||
|
||
logger: logging.Logger | ||
_STATUS_OK = 0 | ||
|
@@ -297,6 +298,16 @@ def create_validation_parser( | |
), | ||
type=str, | ||
) | ||
parser.add_argument( | ||
"--plausability-check", | ||
metavar="<plausability-check>", | ||
choices=["yes", "y"], | ||
help=( | ||
"y/yes if the plausibility of the bom-refs in the" | ||
"sbom should also be checked" | ||
), | ||
type=str, | ||
) | ||
|
||
add_output_argument(parser) | ||
|
||
|
@@ -580,27 +591,40 @@ def has_target() -> bool: | |
|
||
|
||
def invoke_validate(args: argparse.Namespace) -> int: | ||
logger_validate = logging.getLogger(__name__) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why introduce a new logger and not use the existing? |
||
sbom, file_type = read_sbom(args.input) | ||
if args.output is None: | ||
output = Path("./issues.json") | ||
else: | ||
output = args.output | ||
report_format = args.report_format | ||
return ( | ||
_STATUS_OK | ||
if validate_sbom( | ||
sbom=sbom, | ||
input_format=file_type, | ||
file=Path(args.input), | ||
report_format=report_format, | ||
output=output, | ||
schema_type=args.schema_type, | ||
filename_regex=args.filename_pattern, | ||
schema_path=args.schema_path, | ||
) | ||
== _STATUS_OK | ||
else _STATUS_VALIDATION_ERROR | ||
) | ||
sorted_errors = validate_sbom( | ||
sbom=sbom, | ||
input_format=file_type, | ||
file=Path(args.input), | ||
schema_type=args.schema_type, | ||
filename_regex=args.filename_pattern, | ||
schema_path=args.schema_path, | ||
plausability_check=args.plausability_check, | ||
) | ||
if len(sorted_errors) == 0: | ||
logger_validate.info("SBOM is compliant to the provided specification schema") | ||
return 0 | ||
else: | ||
if report_format == "warnings-ng": | ||
warnings_ng_handler = WarningsNgReporter(Path(args.input), output) | ||
logger_validate.addHandler(warnings_ng_handler) | ||
for error in sorted_errors: | ||
logger_validate.error( | ||
LogMessage( | ||
message="Invalid SBOM", | ||
description=error.replace( | ||
error[0 : error.find("has the mistake")], "" | ||
).replace("has the mistake: ", ""), | ||
module_name=error[0 : error.find("has the mistake") - 1], | ||
) | ||
) | ||
return _STATUS_OK if sorted_errors == set() else _STATUS_VALIDATION_ERROR | ||
|
||
|
||
def invoke_build_public_bom(args: argparse.Namespace) -> int: | ||
|
This file contains 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 |
---|---|---|
|
@@ -4,6 +4,11 @@ | |
from importlib import resources | ||
from pathlib import Path | ||
|
||
from cdxev.auxiliary.identity import ComponentIdentity | ||
from cdxev.auxiliary.sbomFunctions import ( | ||
get_bom_refs_from_components, | ||
get_component_by_ref, | ||
) | ||
from cdxev.error import AppError | ||
|
||
|
||
|
@@ -106,3 +111,226 @@ def get_external_schema(schema_path: Path) -> tuple[dict, Path]: | |
"Could not load schema", | ||
("Path to the provided schema does not exist"), | ||
) | ||
|
||
|
||
def get_non_unique_bom_refs(sbom: dict) -> list: | ||
list_of_bomrefs = get_bom_refs_from_components(sbom.get("components", [])) | ||
list_of_bomrefs.append( | ||
sbom.get("metadata", {}).get("component", {}).get("bom-ref", "") | ||
) | ||
non_unique_bom_refs = [ | ||
bom_ref for bom_ref in list_of_bomrefs if list_of_bomrefs.count(bom_ref) > 1 | ||
] | ||
return list(set(non_unique_bom_refs)) | ||
|
||
|
||
def create_error_non_unique_bom_ref(reference: str, sbom: dict) -> str: | ||
""" | ||
Function to create an error dict for not unique bom-refs. | ||
|
||
:param str reference: the not unique bom-ref | ||
:param sbom : the sbom the bom-ref originates from | ||
|
||
:return: dict with error message and error description | ||
""" | ||
list_of_all_components = sbom.get("components", []).copy() | ||
list_of_all_components.append(sbom.get("metadata", {}).get("component", {})) | ||
list_of_component_ids = [] | ||
for component in list_of_all_components: | ||
if component.get("bom-ref", "") == reference: | ||
list_of_component_ids.append( | ||
ComponentIdentity.create(component, allow_unsafe=True) | ||
) | ||
component_description_string = "" | ||
for component_id in list_of_component_ids: | ||
component_description_string += f"({component_id})" | ||
error = ( | ||
"SBOM has the mistake: found non unique bom-ref. " | ||
+ f"The reference ({reference}) is used in several components. Those are" | ||
+ component_description_string | ||
) | ||
return error | ||
|
||
|
||
def get_errors_for_non_unique_bomrefs(sbom: dict) -> list: | ||
list_of_non_unique_bomrefs = get_non_unique_bom_refs(sbom) | ||
errors = [] | ||
for reference in list_of_non_unique_bomrefs: | ||
errors.append(create_error_non_unique_bom_ref(reference, sbom)) | ||
return errors | ||
|
||
|
||
def plausibility_check(sbom: dict) -> list: | ||
""" | ||
Check a sbom for plausability. | ||
The sbom is checked for orphaned bom-refs and | ||
components that depend on themself. | ||
|
||
|
||
:param dict sbom: the sbom. | ||
|
||
:return: 0 if no errors were found, 1 otherwise | ||
:rtype: int | ||
""" | ||
orphaned_bom_refs_errors = check_for_orphaned_bom_refs(sbom) | ||
dependencies_bom_refs = check_logic_of_dependencies(sbom) | ||
united_errors = orphaned_bom_refs_errors + dependencies_bom_refs | ||
return united_errors | ||
|
||
|
||
def check_for_orphaned_bom_refs(sbom: dict) -> list[str]: | ||
""" | ||
Check a sbom for orphaned bom-refs, references that do | ||
not correspond to any component from the sbom. | ||
|
||
:param dict sbom: the sbom. | ||
|
||
:return: list with the notifications of found errors | ||
rtype: list[dict] | ||
""" | ||
list_of_actual_bom_refs = get_bom_refs_from_components(sbom.get("components", [])) | ||
list_of_actual_bom_refs.append( | ||
sbom.get("metadata", {}).get("component", {}).get("bom-ref") | ||
) | ||
# Check if bom_refs appear in the sbom, that do not | ||
# correspond to a component from the sbom | ||
|
||
# check dependencies | ||
errors = [] | ||
list_of_all_components = sbom.get("components", []).copy() | ||
list_of_all_components.append(sbom.get("metadata", {}).get("component", {})) | ||
for dependency in sbom.get("dependencies", []): | ||
if dependency.get("ref", "") in list_of_actual_bom_refs: | ||
for bom_ref in dependency.get("dependsOn", []): | ||
if bom_ref not in list_of_actual_bom_refs: | ||
component = get_component_by_ref( | ||
dependency.get("ref", ""), list_of_all_components | ||
) | ||
id = ComponentIdentity.create(component, allow_unsafe=True) | ||
errors.append( | ||
create_error_orphaned_bom_ref( | ||
bom_ref, | ||
"dependencies-dependsOn of reference" | ||
+ dependency.get("ref", "") | ||
+ f" belonging to component ({id})", | ||
) | ||
) | ||
|
||
else: | ||
errors.append( | ||
create_error_orphaned_bom_ref(dependency.get("ref", ""), "dependencies") | ||
) | ||
|
||
# check compositions | ||
for composition in sbom.get("compositions", []): | ||
for reference in composition.get("assemblies", []): | ||
if reference not in list_of_actual_bom_refs: | ||
errors.append(create_error_orphaned_bom_ref(reference, "compositions")) | ||
for reference in composition.get("dependencies", []): | ||
if reference not in list_of_actual_bom_refs: | ||
errors.append(create_error_orphaned_bom_ref(reference, "compositions")) | ||
# check vulnearabilities | ||
for vulnerability in sbom.get("vulnerabilities", []): | ||
for affected in vulnerability.get("affects", []): | ||
if affected.get("ref", "") not in list_of_actual_bom_refs: | ||
errors.append( | ||
create_error_orphaned_bom_ref( | ||
affected.get("ref", ""), | ||
"vulnerabilitie " + vulnerability.get("id", ""), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. vulnerability |
||
) | ||
) | ||
return errors | ||
|
||
|
||
def check_logic_of_dependencies(sbom: dict) -> list[str]: | ||
""" | ||
The function checks if the sbom contains circular dependencies, | ||
e.g. components, that depend on themself. | ||
|
||
:param dict sbom: the sbom | ||
:return: list with the notifications of found errors | ||
:rtype: list[dict] | ||
""" | ||
errors = [] | ||
list_of_actual_bom_refs = get_bom_refs_from_components(sbom.get("components", [])) | ||
list_of_actual_bom_refs.append( | ||
sbom.get("metadata", {}).get("component", {}).get("bom-ref") | ||
) | ||
# Check for circular references in dependencies | ||
for current_reference in list_of_actual_bom_refs: | ||
list_of_upstream_references = get_upstream_dependency_bom_refs( | ||
current_reference, sbom.get("dependencies", []) | ||
) | ||
if current_reference in list_of_upstream_references: | ||
errors.append(create_error_circular_reference(current_reference, sbom)) | ||
return errors | ||
|
||
|
||
def create_error_orphaned_bom_ref(reference: str, found_in: str) -> str: | ||
""" | ||
Function to create an error dict if orphaned bom_refs were found. | ||
|
||
:param str reference: the orphaned reference | ||
:param str found in: location of the orphaned sbom | ||
|
||
:return: dict with error message and error description | ||
""" | ||
error = ( | ||
f"{found_in} has the mistake: found orphaned bom-ref" | ||
f"The reference ({reference}) does not" | ||
" correspond to any component in the sbom." | ||
) | ||
return error | ||
|
||
|
||
def create_error_circular_reference(reference: str, sbom: dict) -> str: | ||
""" | ||
Function that creates an error dict if a selfdependend reference was found. | ||
|
||
:param str reference: the reference that depends on itself | ||
:param dict sbom: the sbom | ||
|
||
:return: dict with error message and error description | ||
""" | ||
list_of_all_components = sbom.get("components", []).copy() | ||
list_of_all_components.append(sbom.get("metadata", {}).get("component", {})) | ||
component = get_component_by_ref(reference, list_of_all_components) | ||
id = ComponentIdentity.create(component, allow_unsafe=True) | ||
error = ( | ||
"dependencies has the mistake: found circular reference (selfdependent component)" | ||
f"The component ({id}) depends on itself" | ||
) | ||
return error | ||
|
||
|
||
def get_upstream_dependency_bom_refs( | ||
start_reference: str, list_of_dependencies: list[dict], recursion_depth: int = 0 | ||
) -> list: | ||
""" | ||
Function that returns the upstream dependencies of a component, | ||
also all the components this component depends on. | ||
|
||
:param str start_reference: reference from which to start the recursion | ||
return every reference this component depends on. | ||
:param dict sbom: the sbom | ||
:recursion_depth: parameter for the internal recursion. | ||
|
||
:return: list with elements the component depends on. | ||
:rtype: list[str] | ||
""" | ||
list_with_dependencies = [] | ||
# prevent endless recursion, max recursion number is qual to the maximal debt | ||
# of the tree, also the number of dependencies given | ||
if recursion_depth < len(list_of_dependencies) + 1: | ||
recursion_depth += 1 | ||
for dependency in list_of_dependencies: | ||
if dependency.get("ref", "") == start_reference: | ||
for reference in dependency.get("dependsOn", ""): | ||
list_with_dependencies.append(reference) | ||
new_deps = get_upstream_dependency_bom_refs( | ||
reference, list_of_dependencies, recursion_depth | ||
) | ||
for ref in new_deps: | ||
if ref not in list_with_dependencies: | ||
list_with_dependencies.append(ref) | ||
return list_with_dependencies |
This file contains 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
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.
Why this additional choice? Why not providing the flag
plausibility-check
meanstrue
?