Skip to content

Commit

Permalink
Merge pull request #1171 from lcarva/EC-883
Browse files Browse the repository at this point in the history
Add support for SPDX SBOMs in rpm_repos package
  • Loading branch information
lcarva authored Oct 8, 2024
2 parents b704804 + 18fd18d commit a6e8618
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 37 deletions.
2 changes: 2 additions & 0 deletions policy/lib/sbom.rego
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import rego.v1
# image's filesystem. This fallback exists for legacy purposes and support for it will be removed
# soon.

all_sboms := array.concat(cyclonedx_sboms, spdx_sboms)

default cyclonedx_sboms := []

cyclonedx_sboms := sboms if {
Expand Down
6 changes: 6 additions & 0 deletions policy/lib/sbom_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import rego.v1
import data.lib
import data.lib.sbom

test_all_sboms if {
expected := ["hurricane", "tornado", "spandex", "latex"]
lib.assert_equal(sbom.all_sboms, expected) with sbom.cyclonedx_sboms as ["hurricane", "tornado"]
with sbom.spdx_sboms as ["spandex", "latex"]
}

# test from attestation and fallback to oci image
test_cyclonedx_sboms if {
attestations := [
Expand Down
55 changes: 47 additions & 8 deletions policy/release/rpm_repos.rego
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,48 @@ all_c2_purls_with_repo_ids contains purl_obj if {
# different sboms are merged together to produce the final sbom.)
#
all_c2_rpm_purls contains purl if {
some sbom in _all_sboms
some component in sbom.components
some entity in all_rpm_entities
entity.found_by_cachi2
purl := entity.purl
}

all_rpm_entities contains entity if {
some sbom in lib.sbom.all_sboms
some entity in _rpm_entities(sbom)
}

_rpm_entities(sbom) := entities if {
# CycloneDX
entities := {entity |
some component in sbom.components
purl := component.purl
_is_rpmish(purl)
entity := {
"purl": purl,
"found_by_cachi2": _component_found_by_cachi2(component),
}
}
count(entities) > 0
} else := entities if {
# SPDX
entities := {entity |
some pkg in sbom.packages
some ref in pkg.externalRefs
ref.referenceType == "purl"
ref.referenceCategory == "PACKAGE-MANAGER"
purl := ref.referenceLocator
_is_rpmish(purl)
entity := {
"purl": purl,
"found_by_cachi2": _package_found_by_cachi2(pkg),
}
}
count(entities) > 0
}

_component_found_by_cachi2(component) if {
some property in component.properties
property == _cachi2_found_by_property

purl := component.purl
_is_rpmish(purl)
}

# This is what cachi2 produces in the component property list
Expand All @@ -134,6 +168,14 @@ _cachi2_found_by_property := {
"value": "cachi2",
}

_package_found_by_cachi2(pkg) if {
some annotation in pkg.annotations
annotation.annotator == "cachi2"
annotation.annotationType == "OTHER"
# `comment` contains additional information, but that is not needed for the purpose of
# simply filtering what was found by cachi2.
}

# Match rpms and modules
# (Use a string match instead of parsing it and checking the type)
_is_rpmish(purl) if {
Expand All @@ -142,9 +184,6 @@ _is_rpmish(purl) if {
startswith(purl, "pkg:rpmmod/")
}

# In future there will be SPDX sboms also
_all_sboms := lib.sbom.cyclonedx_sboms

_known_repo_ids := lib.rule_data(_rule_data_key)

_rule_data_key := "known_rpm_repositories"
Expand Down
137 changes: 108 additions & 29 deletions policy/release/rpm_repos_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,39 @@ test_repo_id_data_not_strings if {
}

test_repo_id_all if {
lib.assert_equal_results(
lib.assert_equal(
{p1, p2, p3, p4, p5, p7},
rpm_repos.all_c2_rpm_purls,
) with lib.sbom.all_sboms as fake_cyclonedx_sboms

lib.assert_equal(
{p1, p2, p3, p4, p5, p7},
rpm_repos.all_c2_rpm_purls,
) with rpm_repos._all_sboms as fake_sboms
) with lib.sbom.all_sboms as fake_spdx_sboms
}

test_repo_id_all_with_repo_id if {
lib.assert_equal_results(
lib.assert_equal(
{p1, p2, p3, p7},
rpm_repos._plain_purls(rpm_repos.all_c2_purls_with_repo_ids),
) with rpm_repos._all_sboms as fake_sboms
) with lib.sbom.all_sboms as fake_cyclonedx_sboms

lib.assert_equal(
{p1, p2, p3, p7},
rpm_repos._plain_purls(rpm_repos.all_c2_purls_with_repo_ids),
) with lib.sbom.all_sboms as fake_spdx_sboms
}

test_repo_id_all_known if {
lib.assert_equal_results(
lib.assert_equal(
{p1, p2, p7},
rpm_repos._plain_purls(rpm_repos.all_c2_purls_with_known_repo_ids),
) with rpm_repos._all_sboms as fake_sboms with data.rule_data.known_rpm_repositories as fake_repo_id_list
) with lib.sbom.all_sboms as fake_cyclonedx_sboms with data.rule_data.known_rpm_repositories as fake_repo_id_list

lib.assert_equal(
{p1, p2, p7},
rpm_repos._plain_purls(rpm_repos.all_c2_purls_with_known_repo_ids),
) with lib.sbom.all_sboms as fake_spdx_sboms with data.rule_data.known_rpm_repositories as fake_repo_id_list
}

test_repo_id_purls_missing_repo_ids if {
Expand All @@ -76,7 +91,12 @@ test_repo_id_purls_missing_repo_ids if {
},
}

lib.assert_equal_results(expected, rpm_repos.deny) with rpm_repos._all_sboms as [fake_sbom({p1, p2, p4, p5, p6, p7})]
cyclonedx_sboms := [fake_cyclonedx_sbom({p1, p2, p4, p5, p6, p7})]
lib.assert_equal_results(expected, rpm_repos.deny) with lib.sbom.all_sboms as cyclonedx_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list

spdx_sboms := [fake_spdx_sbom({p1, p2, p4, p5, p6, p7})]
lib.assert_equal_results(expected, rpm_repos.deny) with lib.sbom.all_sboms as spdx_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list
}

Expand All @@ -88,7 +108,13 @@ test_repo_id_purls_missing_repo_ids_truncated if {
"term": "pkg:rpm/borken",
}}

lib.assert_equal_results(expected, rpm_repos.deny) with rpm_repos._all_sboms as [fake_sbom({p1, p2, p4, p5, p6})]
cyclonedx_sboms := [fake_cyclonedx_sbom({p1, p2, p4, p5, p6})]
lib.assert_equal_results(expected, rpm_repos.deny) with lib.sbom.all_sboms as cyclonedx_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list
with rpm_repos._truncate_threshold as 1 with rpm_repos._min_remainder_count as 0

spdx_sboms := [fake_spdx_sbom({p1, p2, p4, p5, p6})]
lib.assert_equal_results(expected, rpm_repos.deny) with lib.sbom.all_sboms as spdx_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list
with rpm_repos._truncate_threshold as 1 with rpm_repos._min_remainder_count as 0
}
Expand All @@ -103,13 +129,35 @@ test_repo_id_purls_unknown_repo_ids if {
"term": "pkg:rpm/redhat/spam@1.2.3?arch=amd64&repository_id=rhel-23-unrecognized-2-rpms",
}

lib.assert_equal_results({expected}, rpm_repos.deny) with rpm_repos._all_sboms as [fake_sbom({p1, p2, p3, p6})]
cyclonedx_sboms := [fake_cyclonedx_sbom({p1, p2, p3, p6})]
lib.assert_equal_results({expected}, rpm_repos.deny) with lib.sbom.all_sboms as cyclonedx_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list

spdx_sboms := [fake_spdx_sbom({p1, p2, p3, p6})]
lib.assert_equal_results({expected}, rpm_repos.deny) with lib.sbom.all_sboms as spdx_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list
}

test_purl_in_multiple_sboms if {
# Same PURL in both CycloneDX and SPDX SBOMs emits a single violation.
expected := {
"code": "rpm_repos.ids_known",
"msg": sprintf("%s %s", [
"RPM repo id check failed: An RPM component in the SBOM specified an unknown or disallowed repository_id:",
"pkg:rpm/redhat/spam@1.2.3?arch=amd64&repository_id=rhel-23-unrecognized-2-rpms",
]),
"term": "pkg:rpm/redhat/spam@1.2.3?arch=amd64&repository_id=rhel-23-unrecognized-2-rpms",
}

all_sboms := [fake_cyclonedx_sbom({p1, p2, p3, p6}), fake_spdx_sbom({p1, p2, p3, p6})]
lib.assert_equal_results({expected}, rpm_repos.deny) with lib.sbom.all_sboms as all_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list
}

test_repo_id_happy_path_cachi2_components if {
# These three sboms are valid, so we see no violations
lib.assert_empty(rpm_repos.deny) with rpm_repos._all_sboms as [fake_sbom({p1, p2, p7})]
# These three purls are valid, so we see no violations
all_sboms := [fake_cyclonedx_sbom({p1, p2, p7}), fake_spdx_sbom({p1, p2, p7})]
lib.assert_empty(rpm_repos.deny) with rpm_repos._all_sboms as all_sboms
with data.rule_data.known_rpm_repositories as fake_repo_id_list
}

Expand Down Expand Up @@ -140,27 +188,58 @@ test_clamp_violation_strings if {
) with rpm_repos._truncate_threshold as 2 with rpm_repos._min_remainder_count as 3
}

test_all_sboms if {
# (Needed for 100% coverage)
lib.assert_equal("spam-1000", rpm_repos._all_sboms) with lib.sbom.cyclonedx_sboms as "spam-1000"
}

fake_sboms := [fake_sbom({p1, p2, p3, p4, p5, p6, p7})]

# regal ignore:line-length
fake_sbom(fake_purls) := {"components": [{"purl": p, "properties": [rpm_repos._cachi2_found_by_property]} | some p in fake_purls]}
fake_cyclonedx_sboms := [fake_cyclonedx_sbom({p1, p2, p3, p4, p5, p6, p7})]

fake_sboms_syft := [fake_sbom_syft({p1, p2, p3, p4, p5, p6, p7})]
fake_cyclonedx_sbom(fake_purls) := {"components": [
{
"purl": p,
"properties": [rpm_repos._cachi2_found_by_property],
} |
some p in fake_purls
]}

# regal ignore:line-length
fake_sbom_syft(fake_purls) := {"components": [{"purl": p, "properties": [_syft_found_by_property]} | some p in fake_purls]}
fake_sboms_syft := [
fake_sbom_cyclonedx_found_by_syft({p1, p2, p3, p4, p5, p6, p7}),
fake_sbom_spdx_found_by_syft({p1, p2, p3, p4, p5, p6, p7}),
]

# It's not specially relevant to the functionality, since we look for the cachi2 property
# and ignore everything else, but this is what we see syft produce for rpm components.
_syft_found_by_property := {
"name": "syft:package:foundBy",
"value": "rpm-db-cataloger",
}
fake_sbom_cyclonedx_found_by_syft(fake_purls) := {"components": [
{
"purl": p,
# It's not specially relevant to the functionality, since we look for the cachi2 property
# and ignore everything else, but this is what we see syft produce for rpm components.
"properties": [{"name": "syft:package:foundBy", "value": "rpm-db-cataloger"}],
} |
some p in fake_purls
]}

fake_sbom_spdx_found_by_syft(fake_purls) := {"packages": [
{
"annotations": [{"annotator": "syft", "annotationType": "OTHER"}],
"externalRefs": [r],
} |
some p in fake_purls
r := {
"referenceType": "purl",
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": p,
}
]}

fake_spdx_sboms := [fake_spdx_sbom({p1, p2, p3, p4, p5, p6, p7})]

fake_spdx_sbom(fake_purls) := {"packages": [
{
"annotations": [{"annotator": "cachi2", "annotationType": "OTHER"}],
"externalRefs": [r],
} |
some p in fake_purls
r := {
"referenceType": "purl",
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": p,
}
]}

fake_repo_id_list := [
"rhel-23-for-spam-9-rpms",
Expand Down

0 comments on commit a6e8618

Please sign in to comment.