Skip to content

Commit 6c704ca

Browse files
authored
Merge pull request #1696 from csasalu/feature/maven_sbom_check
feat: Maven repo allow-list with metadata-driven enforcement
2 parents 92a6957 + b1e2935 commit 6c704ca

8 files changed

Lines changed: 565 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
= All maven artifacts have known repository URLs Package
2+
3+
Each Maven package listed in an SBOM must specify the repository URL that it comes from, and that URL must be present in the list of known and permitted Maven repositories. If no URL is specified, the package is assumed to come from Maven Central.
4+
5+
== Package Name
6+
7+
* `maven_repos`
8+
9+
== Rules Included
10+
11+
[#maven_repos__deny_unpermitted_urls]
12+
=== link:#maven_repos__deny_unpermitted_urls[Known Repository URLs]
13+
14+
Each Maven package listed in an SBOM must specify the repository URL that it comes from, and that URL must be present in the list of known and permitted Maven repositories. If no URL is specified, the package is assumed to come from Maven Central.
15+
16+
*Solution*: The Maven artifact originates from an untrusted or unpermitted repository. To resolve this, ensure the dependency is sourced from a repository defined in the 'allowed_maven_repositories' list in your policy configuration. If the repository is internal, add its URL to the allowed list in rule_data.
17+
18+
* Rule type: [rule-type-indicator failure]#FAILURE#
19+
* FAILURE message: `%s`
20+
* Code: `maven_repos.deny_unpermitted_urls`
21+
* Effective from: `2026-05-10T00:00:00Z`
22+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L35[Source, window="_blank"]
23+
24+
[#maven_repos__policy_data_missing]
25+
=== link:#maven_repos__policy_data_missing[Policy data validation]
26+
27+
Ensures the required allowed_maven_repositories list is provided.
28+
29+
*Solution*: Ensure that 'allowed_maven_repositories' is defined in the rule_data provided to the policy, and that it contains a list of authorized repository URLs.
30+
31+
* Rule type: [rule-type-indicator failure]#FAILURE#
32+
* FAILURE message: `Policy data is missing the required "%s" list`
33+
* Code: `maven_repos.policy_data_missing`
34+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L17[Source, window="_blank"]

antora/docs/modules/ROOT/pages/release_policy.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ a| Include policy rules responsible for validating rule data.
7171

7272
Rules included:
7373

74+
* xref:packages/release_maven_repos.adoc#maven_repos__policy_data_missing[All maven artifacts have known repository URLs: Policy data validation]
7475
* xref:packages/release_attestation_type.adoc#attestation_type__known_attestation_types_provided[Attestation type: Known attestation types provided]
7576
* xref:packages/release_base_image_registries.adoc#base_image_registries__allowed_registries_provided[Base image checks: Allowed base image registry prefixes list was provided]
7677
* xref:packages/release_buildah_build_task.adoc#buildah_build_task__disallowed_platform_patterns_pattern[Buildah build task: disallowed_platform_patterns format]
@@ -238,6 +239,14 @@ Rules included:
238239
* xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__builder_image_param[rpm-ostree Task: Builder image parameter]
239240
* xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__rule_data[rpm-ostree Task: Rule data]
240241

242+
| [#redhat_maven]`redhat_maven`
243+
a| Ruleset for validating artifacts built via Red Hat Maven repositories.
244+
245+
Rules included:
246+
247+
* xref:packages/release_maven_repos.adoc#maven_repos__deny_unpermitted_urls[All maven artifacts have known repository URLs: Known Repository URLs]
248+
* xref:packages/release_maven_repos.adoc#maven_repos__policy_data_missing[All maven artifacts have known repository URLs: Policy data validation]
249+
241250
| [#redhat_rpms]`redhat_rpms`
242251
a| Include the set of policy rules required for building Red Hat RPMs.
243252

@@ -343,6 +352,9 @@ Rules included:
343352
|*Package Name*
344353
|*Description*
345354

355+
| xref:packages/release_maven_repos.adoc[maven_repos]
356+
a| Each Maven package listed in an SBOM must specify the repository URL that it comes from, and that URL must be present in the list of known and permitted Maven repositories. If no URL is specified, the package is assumed to come from Maven Central.
357+
346358
| xref:packages/release_attestation_type.adoc[attestation_type]
347359
a| Sanity checks related to the format of the image build's attestation.
348360

antora/docs/modules/ROOT/partials/release_policy_nav.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
*** xref:release_policy.adoc#minimal[minimal]
55
*** xref:release_policy.adoc#policy_data[policy_data]
66
*** xref:release_policy.adoc#redhat[redhat]
7+
*** xref:release_policy.adoc#redhat_maven[redhat_maven]
78
*** xref:release_policy.adoc#redhat_rpms[redhat_rpms]
89
*** xref:release_policy.adoc#rhtap-multi-ci[rhtap-multi-ci]
910
*** xref:release_policy.adoc#slsa3[slsa3]
1011
** Release Rules
12+
*** xref:packages/release_maven_repos.adoc[All maven artifacts have known repository URLs]
13+
**** xref:packages/release_maven_repos.adoc#maven_repos__deny_unpermitted_urls[Known Repository URLs]
14+
**** xref:packages/release_maven_repos.adoc#maven_repos__policy_data_missing[Policy data validation]
1115
*** xref:packages/release_attestation_type.adoc[Attestation type]
1216
**** xref:packages/release_attestation_type.adoc#attestation_type__deprecated_policy_attestation_format[Deprecated policy attestation format]
1317
**** xref:packages/release_attestation_type.adoc#attestation_type__known_attestation_type[Known attestation type found]

policy/lib/sbom/maven.rego

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# METADATA
2+
# title: Maven Package Extraction
3+
# description: >-
4+
# Extracts Maven packages and their repository URLs from both CycloneDX
5+
# and SPDX SBOM formats.
6+
package lib.sbom
7+
8+
import rego.v1
9+
10+
maven_packages contains pkg if {
11+
some pkg in _cyclonedx_maven_packages
12+
}
13+
14+
maven_packages contains pkg if {
15+
some pkg in _spdx_maven_packages
16+
}
17+
18+
_cyclonedx_maven_packages contains pkg if {
19+
some s in cyclonedx_sboms
20+
some component in s.components
21+
22+
startswith(component.purl, "pkg:maven/")
23+
24+
repos := {ref.url |
25+
some ref in component.externalRefs
26+
ref.type in {"distribution", "artifact-repository"}
27+
}
28+
29+
final_repos := _empty_to_default(repos)
30+
31+
some repo_url in final_repos
32+
pkg := {
33+
"purl": component.purl,
34+
"name": component.name,
35+
"repository_url": repo_url,
36+
}
37+
}
38+
39+
_spdx_maven_packages contains pkg if {
40+
some s in spdx_sboms
41+
some item in s.packages
42+
43+
startswith(item.purl, "pkg:maven/")
44+
45+
repos := {ref.referenceLocator |
46+
some ref in item.externalRefs
47+
ref.referenceType in {"distribution", "repository"}
48+
}
49+
50+
final_repos := _empty_to_default(repos)
51+
52+
some repo_url in final_repos
53+
pkg := {
54+
"purl": item.purl,
55+
"name": item.name,
56+
"repository_url": repo_url,
57+
}
58+
}
59+
60+
# _empty_to_default ensures that packages without explicit repository URLs
61+
# are still processed. If the input repo_set is empty, it returns {""}.
62+
# In the context of this policy, a blank repository URL is considered
63+
# to be Maven Central (https://repo.maven.apache.org/maven2/).
64+
_empty_to_default(repo_set) := repo_set if {
65+
count(repo_set) > 0
66+
} else := {""}

policy/lib/sbom/maven_test.rego

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package lib.sbom_test
2+
3+
import data.lib.assertions
4+
import data.lib.sbom
5+
6+
test_cyclonedx_maven_extraction if {
7+
mock_components := [{
8+
"name": "auth-lib",
9+
"purl": "pkg:maven/org.example/auth@1.0",
10+
"externalRefs": [{"type": "distribution", "url": "https://repo.maven.apache.org/maven2/"}],
11+
}]
12+
13+
expected := {{
14+
"name": "auth-lib",
15+
"purl": "pkg:maven/org.example/auth@1.0",
16+
"repository_url": "https://repo.maven.apache.org/maven2/",
17+
}}
18+
19+
result := sbom.maven_packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
20+
21+
assertions.assert_equal(expected, result)
22+
}
23+
24+
test_cyclonedx_ignores_non_maven if {
25+
mock_components := [{"name": "react", "purl": "pkg:npm/react@18.2.0"}]
26+
27+
assertions.assert_empty(sbom.maven_packages) with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
28+
}
29+
30+
test_cyclonedx_empty_repo_url if {
31+
mock_components := [{
32+
"name": "no-repo",
33+
"purl": "pkg:maven/org.example/no-repo@1.0",
34+
"externalRefs": [],
35+
}]
36+
37+
expected := {{
38+
"name": "no-repo",
39+
"purl": "pkg:maven/org.example/no-repo@1.0",
40+
"repository_url": "",
41+
}}
42+
43+
result := sbom.maven_packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
44+
45+
assertions.assert_equal(expected, result)
46+
}
47+
48+
test_spdx_maven_extraction if {
49+
mock_packages := [{
50+
"name": "data-service",
51+
"purl": "pkg:maven/org.example/data@2.5",
52+
"externalRefs": [{
53+
"referenceType": "repository",
54+
"referenceLocator": "https://internal.jfrog.io/artifactory",
55+
}],
56+
}]
57+
58+
expected := {{
59+
"name": "data-service",
60+
"purl": "pkg:maven/org.example/data@2.5",
61+
"repository_url": "https://internal.jfrog.io/artifactory",
62+
}}
63+
64+
result := sbom.maven_packages with sbom.spdx_sboms as [_spdx_sbom(mock_packages)]
65+
66+
assertions.assert_equal(expected, result)
67+
}
68+
69+
test_combined_sources if {
70+
mock_cdx := [{
71+
"name": "cdx-pkg",
72+
"purl": "pkg:maven/cdx/pkg@1",
73+
"externalRefs": [{"type": "distribution", "url": "url1"}],
74+
}]
75+
76+
mock_spdx := [{
77+
"name": "spdx-pkg",
78+
"purl": "pkg:maven/spdx/pkg@1",
79+
"externalRefs": [{
80+
"referenceType": "repository",
81+
"referenceLocator": "url2",
82+
}],
83+
}]
84+
85+
expected := {
86+
{
87+
"name": "cdx-pkg",
88+
"purl": "pkg:maven/cdx/pkg@1",
89+
"repository_url": "url1",
90+
},
91+
{
92+
"name": "spdx-pkg",
93+
"purl": "pkg:maven/spdx/pkg@1",
94+
"repository_url": "url2",
95+
},
96+
}
97+
98+
result := sbom.maven_packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_cdx)]
99+
with sbom.spdx_sboms as [_spdx_sbom(mock_spdx)]
100+
101+
assertions.assert_equal(expected, result)
102+
}
103+
104+
test_cyclonedx_multiple_repo_capture if {
105+
mock_components := [{
106+
"name": "multi-repo-lib",
107+
"purl": "pkg:maven/org.example/multi@1.0",
108+
"externalRefs": [
109+
{"type": "distribution", "url": "https://repo-a.com"},
110+
{"type": "artifact-repository", "url": "https://repo-b.com"},
111+
],
112+
}]
113+
114+
expected := {
115+
{
116+
"name": "multi-repo-lib",
117+
"purl": "pkg:maven/org.example/multi@1.0",
118+
"repository_url": "https://repo-a.com",
119+
},
120+
{
121+
"name": "multi-repo-lib",
122+
"purl": "pkg:maven/org.example/multi@1.0",
123+
"repository_url": "https://repo-b.com",
124+
},
125+
}
126+
127+
result := sbom.maven_packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
128+
129+
assertions.assert_equal(expected, result)
130+
}
131+
132+
_cyclonedx_sbom(components) := {"components": components}
133+
134+
_spdx_sbom(packages) := {"packages": packages}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#
2+
# METADATA
3+
# title: redhat_maven
4+
# description: >-
5+
# Ruleset for validating artifacts built via Red Hat Maven repositories.
6+
package collection.redhat_maven
7+
8+
import rego.v1
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# METADATA
2+
# title: All maven artifacts have known repository URLs
3+
# description: >-
4+
# Each Maven package listed in an SBOM must specify the repository URL that it
5+
# comes from, and that URL must be present in the list of known and permitted
6+
# Maven repositories. If no URL is specified, the package is assumed to come
7+
# from Maven Central.
8+
package release.maven_repos
9+
10+
import rego.v1
11+
12+
import data.lib
13+
import data.lib.metadata
14+
import data.lib.rule_data
15+
import data.lib.sbom
16+
17+
# METADATA
18+
# title: Policy data validation
19+
# description: Ensures the required allowed_maven_repositories list is provided.
20+
# custom:
21+
# short_name: policy_data_missing
22+
# failure_msg: Policy data is missing the required "%s" list
23+
# solution: >-
24+
# Ensure that 'allowed_maven_repositories' is defined in the rule_data
25+
# provided to the policy, and that it contains a list of authorized
26+
# repository URLs.
27+
# collections:
28+
# - redhat_maven
29+
# - policy_data
30+
deny contains result if {
31+
some key in _rule_data_errors
32+
result := lib.result_helper(rego.metadata.chain(), [key])
33+
}
34+
35+
# METADATA
36+
# title: Known Repository URLs
37+
# description: >-
38+
# Each Maven package listed in an SBOM must specify the repository URL that it
39+
# comes from, and that URL must be present in the list of known and permitted
40+
# Maven repositories. If no URL is specified, the package is assumed to come
41+
# from Maven Central.
42+
# custom:
43+
# short_name: deny_unpermitted_urls
44+
# failure_msg: '%s'
45+
# solution: >-
46+
# The Maven artifact originates from an untrusted or unpermitted repository.
47+
# To resolve this, ensure the dependency is sourced from a repository defined
48+
# in the 'allowed_maven_repositories' list in your policy configuration.
49+
# If the repository is internal, add its URL to the allowed list in rule_data.
50+
# effective_on: 2026-05-10T00:00:00Z
51+
# collections:
52+
# - redhat_maven
53+
deny contains result if {
54+
some err in _repo_url_errors
55+
result := metadata.result_helper_with_term(rego.metadata.chain(), [err.msg], err.purl)
56+
}
57+
58+
_repo_url_errors contains err if {
59+
some pkg in sbom.maven_packages
60+
source := _get_effective_url(pkg.repository_url)
61+
not _url_is_permitted(source)
62+
err := {
63+
"purl": pkg.purl,
64+
"msg": sprintf("Package %q (source: %q) is not in the permitted list", [pkg.purl, source]),
65+
}
66+
}
67+
68+
_get_effective_url(url) := url if {
69+
url != ""
70+
} else := "https://repo.maven.apache.org/maven2/"
71+
72+
_url_is_permitted(url) if {
73+
permitted := rule_data.get("allowed_maven_repositories")
74+
url in permitted
75+
}
76+
77+
_rule_data_errors contains key if {
78+
key := "allowed_maven_repositories"
79+
data_list := rule_data.get(key)
80+
_is_invalid_data(data_list)
81+
}
82+
83+
_is_invalid_data(val) if not is_array(val)
84+
85+
_is_invalid_data(val) if {
86+
is_array(val)
87+
count(val) == 0
88+
}

0 commit comments

Comments
 (0)