Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions antora/docs/modules/ROOT/pages/release_policy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ Rules included:
* xref:packages/release_git_branch.adoc#git_branch__git_branch[Git branch checks: Builds have a trusted target branch]
* xref:packages/release_provenance_materials.adoc#provenance_materials__git_clone_source_matches_provenance[Provenance Materials: Git clone source matches materials provenance]
* xref:packages/release_provenance_materials.adoc#provenance_materials__git_clone_task_found[Provenance Materials: Git clone task found]
* xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__download_location_valid[RPM Build Dependencies: Builds have valid download locations]
* xref:packages/release_rpm_pipeline.adoc#rpm_pipeline__invalid_pipeline[RPM Pipeline: Task version invalid_pipeline]
* xref:packages/release_rpm_repos.adoc#rpm_repos__ids_known[RPM Repos: All rpms have known repo ids]
* xref:packages/release_rpm_repos.adoc#rpm_repos__rule_data_provided[RPM Repos: Known repo id list provided]
Expand Down Expand Up @@ -394,6 +395,9 @@ a| Policies to prevent releasing an image to quay that has a quay expiration dat
| xref:packages/release_rhtap_multi_ci.adoc[rhtap_multi_ci]
a| Checks for images built using an RHTAP build pipeline in either Jenkins, GitLab or GitHub. RHTAP pipelines are defined under https://github.com/redhat-appstudio/tssc-sample-templates/tree/main/skeleton/ci

| xref:packages/release_rpm_build_deps.adoc[rpm_build_deps]
a| Checks different properties of the CycloneDX SBOMs associated with the image being validated.

| xref:packages/release_rpm_packages.adoc[rpm_packages]
a| Rules used to verify different properties of specific RPM packages found in the SBOM of the image being validated.

Expand Down
2 changes: 2 additions & 0 deletions antora/docs/modules/ROOT/partials/release_policy_nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
*** xref:packages/release_rhtap_multi_ci.adoc[RHTAP Multi-CI]
**** xref:packages/release_rhtap_multi_ci.adoc#rhtap_multi_ci__attestation_format[SLSA Provenance Attestation Format]
**** xref:packages/release_rhtap_multi_ci.adoc#rhtap_multi_ci__attestation_found[SLSA Provenance Attestation Found]
*** xref:packages/release_rpm_build_deps.adoc[RPM Build Dependencies]
**** xref:packages/release_rpm_build_deps.adoc#rpm_build_deps__download_location_valid[Builds have valid download locations]
*** xref:packages/release_rpm_packages.adoc[RPM Packages]
**** xref:packages/release_rpm_packages.adoc#rpm_packages__unique_version[Unique Version]
*** xref:packages/release_rpm_pipeline.adoc[RPM Pipeline]
Expand Down
36 changes: 36 additions & 0 deletions policy/release/rpm_build_deps/rpm_build_deps.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#
# METADATA
# title: RPM Build Dependencies
# description: >-
# Checks different properties of the CycloneDX SBOMs associated with the image being validated.
#
package rpm_build_deps

import rego.v1

import data.lib
import data.lib.sbom

# METADATA
# title: Builds have valid download locations
# description: Builds have valid download locations for RPM build dependencies
# custom:
# short_name: download_location_valid
# failure_msg: Download Location is %s which is not in %v
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# failure_msg: Download Location is %s which is not in %v
# failure_msg: RPM build dependency source %s is not in the allowed list: %v.
# solution: >-
# The list of allowed RPM build dependency sources can be set via the
# `allowed_rpm_build_dependency_sources` rule data.

# collections:
# - redhat_rpms
warn contains result if {
some s in sbom.spdx_sboms
some pkg in s.packages

# NOASSERTION is displayed in the SBOM for the RPMS that have been built
valid_locations := array.concat(["NOASSERTION"], lib.rule_data("allowed_rpm_build_dependency_sources"))
Copy link
Member

@simonbaird simonbaird Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not a blocker, but it might be tidier and perhaps more efficient to move the concat outside the rule. It's a pattern we follow elsewhere


warn ... {
   ...
   not matches_any(pkg.downloadLocation, _valid_locations)
}

# NOASSERTION is displayed in the SBOM for the RPMS that have been built
_valid_locations := array.concat(["NOASSERTION"], lib.rule_data("allowed_rpm_build_dependency_sources"))

not matches_any(pkg.downloadLocation, valid_locations)
result := lib.result_helper(rego.metadata.chain(), [pkg.downloadLocation, valid_locations])
}

matches_any(branch, valid_locations) if {
Copy link
Member

@simonbaird simonbaird Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but I'd suggest making the params more generic. "Branch" seems confusing in this context.

Suggested change
matches_any(branch, valid_locations) if {
matches_any(value, patterns) if {

And maybe we flip the order so it's consistent with regex.match:

Suggested change
matches_any(branch, valid_locations) if {
matches_any(patterns, value) if {

# some pattern in lib.rule_data("allowed_target_branch_patterns")
some pattern in valid_locations
regex.match(pattern, branch)
}
237 changes: 237 additions & 0 deletions policy/release/rpm_build_deps/rpm_build_deps_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#
# METADATA
# title: RPM Build Dependencies tests
# description: >-
# Tests for rpm_build_deps policy
#
package rpm_build_deps_test

import rego.v1

import data.lib
import data.rpm_build_deps

# Test with valid download location - NOASSERTION (always allowed)
test_valid_download_location_noassertion if {
att := _sbom_attestation_with_download_location("NOASSERTION")
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with valid download location - brewroot pattern
test_valid_download_location_brewroot if {
att := _sbom_attestation_with_download_location("https://download.devel.redhat.com/brewroot/repos/some-package.rpm")
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with valid download location - codeload pattern
test_valid_download_location_codeload if {
att := _sbom_attestation_with_download_location("https://codeload.github.com/user/repo/tar.gz/v1.0.0")
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with valid download location - pypi pattern
test_valid_download_location_pypi if {
att := _sbom_attestation_with_download_location("https://files.pythonhosted.org/packages/some-package-1.0.tar.gz")
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with valid download location - maven central pattern
test_valid_download_location_maven if {
location := "https://repo.maven.apache.org/maven2/org/example/artifact/1.0/artifact-1.0.jar"
att := _sbom_attestation_with_download_location(location)
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with invalid download location - doesn't match any allowed pattern
test_invalid_download_location if {
invalid_location := "https://untrusted.example.com/package.rpm"
att := _sbom_attestation_with_download_location(invalid_location)
expected_locations := array.concat(["NOASSERTION"], _mock_allowed_locations)
expected := {{
"code": "rpm_build_deps.download_location_valid",
"msg": sprintf("Download Location is %s which is not in %v", [invalid_location, expected_locations]),
}}
lib.assert_equal_results(expected, rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with another invalid download location
test_invalid_download_location_wrong_domain if {
invalid_location := "https://malicious.org/package.rpm"
att := _sbom_attestation_with_download_location(invalid_location)
expected_locations := array.concat(["NOASSERTION"], _mock_allowed_locations)
expected := {{
"code": "rpm_build_deps.download_location_valid",
"msg": sprintf("Download Location is %s which is not in %v", [invalid_location, expected_locations]),
}}
lib.assert_equal_results(expected, rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate of test_invalid_download_location, can be removed


# Test with location that doesn't match the patterns
test_invalid_download_location_no_pattern_match if {
# This location doesn't match any of the allowed patterns
invalid_location := "https://example.com/package.rpm"
att := _sbom_attestation_with_download_location(invalid_location)
results := rpm_build_deps.warn with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
count(results) > 0
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate of test_invalid_download_location, can be removed


# Test with multiple packages - all valid
test_multiple_packages_all_valid if {
att := _sbom_attestation_with_multiple_packages([
"NOASSERTION",
"https://download.devel.redhat.com/brewroot/repos/package1.rpm",
"https://codeload.github.com/org/repo/tar.gz/v2.0",
])
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with multiple packages - one invalid
test_multiple_packages_one_invalid if {
att := _sbom_attestation_with_multiple_packages([
"NOASSERTION",
"https://untrusted.example.com/package.rpm",
"https://download.devel.redhat.com/brewroot/repos/package2.rpm",
])
results := rpm_build_deps.warn with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
count(results) == 1
}

# Test with multiple packages - all invalid
test_multiple_packages_all_invalid if {
att := _sbom_attestation_with_multiple_packages([
"https://untrusted1.example.com/package1.rpm",
"https://untrusted2.example.com/package2.rpm",
])
results := rpm_build_deps.warn with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
count(results) == 2
}

# Test with empty SBOM
test_empty_sbom if {
att := {"statement": {
"predicateType": "https://spdx.dev/Document",
"predicate": {
"spdxVersion": "SPDX-2.3",
"packages": [],
},
}}
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as _mock_allowed_locations
}

# Test with empty rule_data - should only allow NOASSERTION
test_empty_rule_data_warns_urls if {
att := _sbom_attestation_with_download_location("https://download.devel.redhat.com/brewroot/repos/package.rpm")
results := rpm_build_deps.warn with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as []
count(results) == 1
}

# Test NOASSERTION is always allowed even with empty rule_data
test_noassertion_allowed_with_empty_rule_data if {
att := _sbom_attestation_with_download_location("NOASSERTION")
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as []
}

# Test with custom rule_data
test_custom_rule_data if {
custom_locations := ["^https://custom\\.example\\.com/.*", "^https://archive\\.example\\.org/.*"]
att := _sbom_attestation_with_download_location("https://custom.example.com/packages/foo.rpm")
lib.assert_empty(rpm_build_deps.warn) with input.attestations as [att]
with data.rule_data.allowed_rpm_build_dependency_sources as custom_locations
}

# Test matches_any function - valid with exact match
test_matches_any_exact_match if {
rpm_build_deps.matches_any("NOASSERTION", ["NOASSERTION"])
}

# Test matches_any function - valid with regex pattern
test_matches_any_regex_pattern if {
rpm_build_deps.matches_any(
"https://download.devel.redhat.com/brewroot/repos/package.rpm",
["^https://download\\.devel\\.redhat\\.com/brewroot/repos/.*"],
)
}

# Test matches_any function - invalid
test_matches_any_invalid if {
not rpm_build_deps.matches_any("https://untrusted.example.com/package.rpm", _mock_allowed_locations)
}

# Test matches_any with multiple patterns
test_matches_any_multiple_patterns if {
patterns := [
"^https://download\\.devel\\.redhat\\.com/.*",
"^https://codeload\\.github\\.com/.*",
]
rpm_build_deps.matches_any("https://codeload.github.com/user/repo/tar.gz/v1.0", patterns)
}

# Helper function to create SBOM attestation with specific download location
_sbom_attestation_with_download_location(location) := {"statement": {
"predicateType": "https://spdx.dev/Document",
"predicate": {
"spdxVersion": "SPDX-2.3",
"documentNamespace": "https://example.dev/spdxdocs/example-123",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-sbom",
"creationInfo": {
"created": "2024-01-01T00:00:00Z",
"creators": ["Tool: test"],
},
"packages": [{
"SPDXID": "SPDXRef-Package-test",
"name": "test-package",
"versionInfo": "1.0.0",
"downloadLocation": location,
}],
},
}}

# Helper function to create SBOM attestation with multiple packages
_sbom_attestation_with_multiple_packages(locations) := {"statement": {
"predicateType": "https://spdx.dev/Document",
"predicate": {
"spdxVersion": "SPDX-2.3",
"documentNamespace": "https://example.dev/spdxdocs/example-456",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "test-sbom-multi",
"creationInfo": {
"created": "2024-01-01T00:00:00Z",
"creators": ["Tool: test"],
},
"packages": [pkg |
some i, location in locations
pkg := {
"SPDXID": sprintf("SPDXRef-Package-%d", [i]),
"name": sprintf("package-%d", [i]),
"versionInfo": "1.0.0",
"downloadLocation": location,
}
],
},
}}

# Mock rule data - allowed source locations for testing
# These patterns represent common trusted sources for RPM build dependencies
_mock_allowed_locations := [
"^https://download\\.devel\\.redhat\\.com/brewroot/repos/.*",
"^https://codeload\\.github\\.com/.*",
"^https://files\\.pythonhosted\\.org/.*",
"^https://repo\\.maven\\.apache\\.org/maven2/.*",
]