Skip to content

Commit 1f7db5b

Browse files
committed
Support multiple repository URLs per Maven package
Update the Maven SBOM library to process all repository URLs found in a component's external references instead of only the first match. This aligns with SPDX and CycloneDX schemas which allow 0..N external references per package.
1 parent c788b4a commit 1f7db5b

File tree

7 files changed

+174
-86
lines changed

7 files changed

+174
-86
lines changed

antora/docs/modules/ROOT/pages/packages/release_maven_repos.adoc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ Each Maven package listed in an SBOM must specify the repository URL that it com
1111
[#maven_repos__deny_unpermitted_urls]
1212
=== link:#maven_repos__deny_unpermitted_urls[Known Repository URLs]
1313

14-
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.
1515

1616
* Rule type: [rule-type-indicator failure]#FAILURE#
1717
* FAILURE message: `%s`
1818
* Code: `maven_repos.deny_unpermitted_urls`
1919
* Effective from: `2026-05-10T00:00:00Z`
20-
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L34[Source, window="_blank"]
20+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L40[Source, window="_blank"]
2121

2222
[#maven_repos__policy_data_missing]
23-
=== link:#maven_repos__policy_data_missing[Missing Policy Data]
23+
=== link:#maven_repos__policy_data_missing[Policy data validation]
2424

25+
Ensures the required allowed_maven_repositories list is provided.
2526

27+
*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.
2628

2729
* Rule type: [rule-type-indicator failure]#FAILURE#
28-
* FAILURE message: `Policy data is missing the required %q list`
30+
* FAILURE message: `Policy data is missing the required "%s" list`
2931
* Code: `maven_repos.policy_data_missing`
30-
* Effective from: `2026-05-10T00:00:00Z`
3132
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L22[Source, window="_blank"]

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

Lines changed: 1 addition & 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]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
** Release Rules
1212
*** xref:packages/release_maven_repos.adoc[All maven artifacts have known repository URLs]
1313
**** 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[Missing Policy Data]
14+
**** xref:packages/release_maven_repos.adoc#maven_repos__policy_data_missing[Policy data validation]
1515
*** xref:packages/release_attestation_type.adoc[Attestation type]
1616
**** xref:packages/release_attestation_type.adoc#attestation_type__deprecated_policy_attestation_format[Deprecated policy attestation format]
1717
**** xref:packages/release_attestation_type.adoc#attestation_type__known_attestation_type[Known attestation type found]

policy/lib/sbom/maven.rego

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,66 @@
1-
package lib.sbom.maven
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
27

38
import future.keywords.contains
49
import future.keywords.if
510
import future.keywords.in
611

7-
import data.lib.cyclonedx
8-
import data.lib.spdx
9-
10-
# All Maven packages found in the SBOM, regardless of format.
11-
# Each package contains at least: 'purl', 'name', and 'repository_url'.
12+
# This rule merges with 'packages' defined in other files
13+
# under the same package (e.g., rpm.rego)
1214
packages contains pkg if {
13-
some p in _cyclonedx_maven_packages
14-
pkg := p
15+
some pkg in _cyclonedx_maven_packages
1516
}
1617

1718
packages contains pkg if {
18-
some p in _spdx_maven_packages
19-
pkg := p
19+
some pkg in _spdx_maven_packages
2020
}
2121

2222
_cyclonedx_maven_packages contains pkg if {
23-
some component in cyclonedx.packages
23+
some s in cyclonedx_sboms
24+
some component in s.components
25+
2426
startswith(component.purl, "pkg:maven/")
2527

28+
repos := {ref.url |
29+
some ref in component.externalRefs
30+
ref.type in ["distribution", "artifact-repository"]
31+
}
32+
33+
final_repos := _empty_to_default(repos)
34+
35+
some repo_url in final_repos
2636
pkg := {
2737
"purl": component.purl,
2838
"name": component.name,
29-
"repository_url": _extract_cdx_repo(component),
39+
"repository_url": repo_url,
3040
}
3141
}
3242

33-
_extract_cdx_repo(component) := url if {
34-
some ref in component.externalRefs
35-
ref.type in ["distribution", "artifact-repository"]
36-
url := ref.url
37-
}
38-
39-
else := ""
40-
4143
_spdx_maven_packages contains pkg if {
42-
some item in spdx.packages
44+
some s in spdx_sboms
45+
some item in s.packages
46+
4347
startswith(item.purl, "pkg:maven/")
4448

49+
repos := {ref.referenceLocator |
50+
some ref in item.externalRefs
51+
ref.referenceType in ["distribution", "repository"]
52+
}
53+
54+
final_repos := _empty_to_default(repos)
55+
56+
some repo_url in final_repos
4557
pkg := {
4658
"purl": item.purl,
4759
"name": item.name,
48-
"repository_url": _extract_spdx_repo(item),
60+
"repository_url": repo_url,
4961
}
5062
}
5163

52-
_extract_spdx_repo(item) := url if {
53-
some ref in item.externalRefs
54-
ref.referenceType in ["distribution", "repository"]
55-
url := ref.referenceLocator
56-
}
57-
58-
else := ""
64+
_empty_to_default(repo_set) := repo_set if {
65+
count(repo_set) > 0
66+
} else := {""}

policy/lib/sbom/maven_test.rego

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
package lib.sbom.maven_test
1+
package lib.sbom_test
22

3+
import data.lib.sbom
34
import future.keywords.if
45
import future.keywords.in
56

6-
import data.lib.sbom.maven
7-
87
test_cyclonedx_maven_extraction if {
9-
mock_cdx := [{"name": "auth-lib", "purl": "pkg:maven/org.example/auth@1.0", "externalRefs": [{"type": "distribution", "url": "https://repo.maven.apache.org/maven2/"}]}]
8+
mock_components := [{
9+
"name": "auth-lib",
10+
"purl": "pkg:maven/org.example/auth@1.0",
11+
"externalRefs": [{"type": "distribution", "url": "https://repo.maven.apache.org/maven2/"}],
12+
}]
1013

11-
res := maven.packages with data.lib.cyclonedx.packages as mock_cdx
14+
res := sbom.packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
1215

1316
res == {{
1417
"name": "auth-lib",
@@ -18,21 +21,28 @@ test_cyclonedx_maven_extraction if {
1821
}
1922

2023
test_cyclonedx_ignores_non_maven if {
21-
mock_cdx := [{"name": "react", "purl": "pkg:npm/react@18.2.0"}]
22-
res := maven.packages with data.lib.cyclonedx.packages as mock_cdx
24+
mock_components := [{"name": "react", "purl": "pkg:npm/react@18.2.0"}]
25+
26+
res := sbom.packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
27+
2328
count(res) == 0
2429
}
2530

2631
test_cyclonedx_empty_repo_url if {
27-
mock_cdx := [{"name": "no-repo", "purl": "pkg:maven/org.example/no-repo@1.0", "externalRefs": []}]
28-
res := maven.packages with data.lib.cyclonedx.packages as mock_cdx
32+
mock_components := [{
33+
"name": "no-repo",
34+
"purl": "pkg:maven/org.example/no-repo@1.0",
35+
"externalRefs": [],
36+
}]
37+
38+
res := sbom.packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
2939

3040
some pkg in res
3141
pkg.repository_url == ""
3242
}
3343

3444
test_spdx_maven_extraction if {
35-
mock_spdx := [{
45+
mock_packages := [{
3646
"name": "data-service",
3747
"purl": "pkg:maven/org.example/data@2.5",
3848
"externalRefs": [{
@@ -41,7 +51,7 @@ test_spdx_maven_extraction if {
4151
}],
4252
}]
4353

44-
res := maven.packages with data.lib.spdx.packages as mock_spdx
54+
res := sbom.packages with sbom.spdx_sboms as [_spdx_sbom(mock_packages)]
4555

4656
res == {{
4757
"name": "data-service",
@@ -50,25 +60,45 @@ test_spdx_maven_extraction if {
5060
}}
5161
}
5262

53-
test_spdx_empty_repo_url if {
63+
test_combined_sources if {
64+
mock_cdx := [{
65+
"name": "cdx-pkg",
66+
"purl": "pkg:maven/cdx/pkg@1",
67+
"externalRefs": [{"type": "distribution", "url": "url1"}],
68+
}]
69+
5470
mock_spdx := [{
55-
"name": "no-ref",
56-
"purl": "pkg:maven/org.example/no-ref@1.0",
57-
"externalRefs": [{"referenceType": "other", "referenceLocator": "ignore-me"}],
71+
"name": "spdx-pkg",
72+
"purl": "pkg:maven/spdx/pkg@1",
73+
"externalRefs": [{
74+
"referenceType": "repository",
75+
"referenceLocator": "url2",
76+
}],
5877
}]
5978

60-
res := maven.packages with data.lib.spdx.packages as mock_spdx
79+
res := sbom.packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_cdx)]
80+
with sbom.spdx_sboms as [_spdx_sbom(mock_spdx)]
6181

62-
some pkg in res
63-
pkg.repository_url == ""
82+
count(res) == 2
6483
}
6584

66-
test_combined_sources if {
67-
mock_cdx := [{"name": "cdx-pkg", "purl": "pkg:maven/cdx/pkg@1"}]
68-
mock_spdx := [{"name": "spdx-pkg", "purl": "pkg:maven/spdx/pkg@1"}]
85+
test_cyclonedx_multiple_repo_capture if {
86+
mock_components := [{
87+
"name": "multi-repo-lib",
88+
"purl": "pkg:maven/org.example/multi@1.0",
89+
"externalRefs": [
90+
{"type": "distribution", "url": "https://repo-a.com"},
91+
{"type": "artifact-repository", "url": "https://repo-b.com"},
92+
],
93+
}]
6994

70-
res := maven.packages with data.lib.cyclonedx.packages as mock_cdx
71-
with data.lib.spdx.packages as mock_spdx
95+
pkg_list := sbom.packages with sbom.cyclonedx_sboms as [_cyclonedx_sbom(mock_components)]
7296

73-
count(res) == 2
97+
count(pkg_list) == 2
98+
urls := {p.repository_url | some p in pkg_list}
99+
urls == {"https://repo-a.com", "https://repo-b.com"}
74100
}
101+
102+
_cyclonedx_sbom(components) := {"components": components}
103+
104+
_spdx_sbom(packages) := {"packages": packages}

policy/release/maven_repos/maven_repos.rego

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,33 @@ import future.keywords.if
1717
import future.keywords.in
1818

1919
import data.lib
20-
import data.lib.sbom.maven
20+
import data.lib.sbom
2121

2222
# METADATA
23-
# title: Missing Policy Data
24-
# scope: rule
23+
# title: Policy data validation
24+
# description: Ensures the required allowed_maven_repositories list is provided.
2525
# custom:
26-
# short_name: policy_data_missing
27-
# failure_msg: 'Policy data is missing the required %q list'
28-
# effective_on: 2026-05-10T00:00:00Z
26+
# short_name: policy_data_missing
27+
# failure_msg: Policy data is missing the required "%s" list
28+
# solution: >-
29+
# Ensure that 'allowed_maven_repositories' is defined in the rule_data
30+
# provided to the policy, and that it contains a list of authorized
31+
# repository URLs.
32+
# collections:
33+
# - policy_data
34+
# severity: failure
2935
deny contains result if {
3036
some key in _rule_data_errors
3137
result := lib.result_helper(rego.metadata.chain(), [key])
3238
}
3339

3440
# METADATA
3541
# title: Known Repository URLs
42+
# description: >-
43+
# Each Maven package listed in an SBOM must specify the repository URL that it
44+
# comes from, and that URL must be present in the list of known and permitted
45+
# Maven repositories. If no URL is specified, the package is assumed to come
46+
# from Maven Central.
3647
# scope: rule
3748
# custom:
3849
# short_name: deny_unpermitted_urls
@@ -45,7 +56,7 @@ deny contains result if {
4556
}
4657

4758
_repo_url_errors[purl] := msg if {
48-
some pkg in maven.packages
59+
some pkg in sbom.packages
4960
purl := pkg.purl
5061
source := _get_effective_url(pkg.repository_url)
5162
not _url_is_permitted(source)

0 commit comments

Comments
 (0)