Skip to content

Commit 4d11bcd

Browse files
committed
feat: Maven repo allow-list with metadata-driven enforcement
Implemented strict repository URL validation with time-based enforcement handled via policy metadata directives. - Restricted Maven builds to approved repositories only. - Added 'effective_from' metadata annotation to allow for a grace period before hard enforcement.All violations are flagged as warnings until the 'effective_from' date. - Support for SPDX and CycloneDX.
1 parent d8be74d commit 4d11bcd

9 files changed

Lines changed: 379 additions & 0 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
15+
16+
* Rule type: [rule-type-indicator failure]#FAILURE#
17+
* FAILURE message: `%s`
18+
* Code: `maven_repos.deny_unpermitted_urls`
19+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L38[Source, window="_blank"]
20+
21+
[#maven_repos__policy_data_missing]
22+
=== link:#maven_repos__policy_data_missing[Missing Policy Data]
23+
24+
25+
26+
* Rule type: [rule-type-indicator failure]#FAILURE#
27+
* FAILURE message: `Policy data is missing the required %q list`
28+
* Code: `maven_repos.policy_data_missing`
29+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/maven_repos/maven_repos.rego#L27[Source, window="_blank"]

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ a| Include the set of policy rules required for Red Hat products.
103103

104104
Rules included:
105105

106+
* xref:packages/release_maven_repos.adoc#maven_repos_package[All maven artifacts have known repository URLs: All maven artifacts have known repository URLs]
106107
* xref:packages/release_attestation_type.adoc#attestation_type__deprecated_policy_attestation_format[Attestation type: Deprecated policy attestation format]
107108
* xref:packages/release_attestation_type.adoc#attestation_type__known_attestation_type[Attestation type: Known attestation type found]
108109
* xref:packages/release_attestation_type.adoc#attestation_type__known_attestation_types_provided[Attestation type: Known attestation types provided]
@@ -233,6 +234,13 @@ Rules included:
233234
* xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__builder_image_param[rpm-ostree Task: Builder image parameter]
234235
* xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__rule_data[rpm-ostree Task: Rule data]
235236

237+
| [#redhat_maven]`redhat_maven`
238+
a| Include the set of policy rules required for building Red Hat Maven.
239+
240+
Rules included:
241+
242+
* xref:packages/release_maven_repos.adoc#maven_repos_package[All maven artifacts have known repository URLs: All maven artifacts have known repository URLs]
243+
236244
| [#redhat_rpms]`redhat_rpms`
237245
a| Include the set of policy rules required for building Red Hat RPMs.
238246

@@ -344,6 +352,9 @@ Rules included:
344352
|*Package Name*
345353
|*Description*
346354

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+
347358
| xref:packages/release_attestation_type.adoc[attestation_type]
348359
a| Sanity checks related to the format of the image build's attestation.
349360

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[Missing Policy Data]
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/rule_data.rego

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import rego.v1
66
# will take precedence over these defaults.
77
#
88
rule_data_defaults := {
9+
#
10+
# Used in release/maven_repos/maven_repos.rego
11+
"allowed_maven_repositories": [
12+
"https://indy.corp.redhat.com",
13+
"https://maven.repository.redhat.com/ga/",
14+
],
915
#
1016
# Used in release/attestation_type
1117
"known_attestation_types": [

policy/lib/sbom/maven.rego

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package lib.sbom.maven
2+
3+
import future.keywords.contains
4+
import future.keywords.if
5+
import future.keywords.in
6+
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+
packages contains pkg if {
13+
some p in _cyclonedx_maven_packages
14+
pkg := p
15+
}
16+
17+
packages contains pkg if {
18+
some p in _spdx_maven_packages
19+
pkg := p
20+
}
21+
22+
_cyclonedx_maven_packages contains pkg if {
23+
some component in cyclonedx.packages
24+
startswith(component.purl, "pkg:maven/")
25+
26+
pkg := {
27+
"purl": component.purl,
28+
"name": component.name,
29+
"repository_url": _extract_cdx_repo(component),
30+
}
31+
}
32+
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+
41+
_spdx_maven_packages contains pkg if {
42+
some item in spdx.packages
43+
startswith(item.purl, "pkg:maven/")
44+
45+
pkg := {
46+
"purl": item.purl,
47+
"name": item.name,
48+
"repository_url": _extract_spdx_repo(item),
49+
}
50+
}
51+
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 := ""

policy/lib/sbom/maven_test.rego

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package lib.sbom.maven_test
2+
3+
import future.keywords.if
4+
import future.keywords.in
5+
6+
import data.lib.sbom.maven
7+
8+
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/"}]}]
10+
11+
res := maven.packages with data.lib.cyclonedx.packages as mock_cdx
12+
13+
res == {{
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+
20+
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
23+
count(res) == 0
24+
}
25+
26+
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
29+
30+
some pkg in res
31+
pkg.repository_url == ""
32+
}
33+
34+
test_spdx_maven_extraction if {
35+
mock_spdx := [{
36+
"name": "data-service",
37+
"purl": "pkg:maven/org.example/data@2.5",
38+
"externalRefs": [{
39+
"referenceType": "repository",
40+
"referenceLocator": "https://internal.jfrog.io/artifactory",
41+
}],
42+
}]
43+
44+
res := maven.packages with data.lib.spdx.packages as mock_spdx
45+
46+
res == {{
47+
"name": "data-service",
48+
"purl": "pkg:maven/org.example/data@2.5",
49+
"repository_url": "https://internal.jfrog.io/artifactory",
50+
}}
51+
}
52+
53+
test_spdx_empty_repo_url if {
54+
mock_spdx := [{
55+
"name": "no-ref",
56+
"purl": "pkg:maven/org.example/no-ref@1.0",
57+
"externalRefs": [{"referenceType": "other", "referenceLocator": "ignore-me"}],
58+
}]
59+
60+
res := maven.packages with data.lib.spdx.packages as mock_spdx
61+
62+
some pkg in res
63+
pkg.repository_url == ""
64+
}
65+
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"}]
69+
70+
res := maven.packages with data.lib.cyclonedx.packages as mock_cdx
71+
with data.lib.spdx.packages as mock_spdx
72+
73+
count(res) == 2
74+
}
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+
# Include the set of policy rules required for building Red Hat Maven.
6+
package collection.redhat_maven
7+
8+
import rego.v1
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
# custom:
9+
# short_name: deny_unpermitted_urls_1
10+
# failure_msg: 'Maven repo URL check failed: %s'
11+
# solution: >-
12+
# Ensure every maven artifact comes from a known and permitted repository URL.
13+
# collections:
14+
# - redhat
15+
# - redhat_maven
16+
# effective_on: "2026-05-10T00:00:00Z"
17+
#
18+
package release.maven_repos
19+
20+
import future.keywords.contains
21+
import future.keywords.if
22+
import future.keywords.in
23+
24+
import data.lib
25+
import data.lib.sbom.maven
26+
27+
# METADATA
28+
# title: Missing Policy Data
29+
# scope: rule
30+
# custom:
31+
# short_name: policy_data_missing
32+
# failure_msg: 'Policy data is missing the required %q list'
33+
deny contains result if {
34+
some key in _rule_data_errors
35+
result := lib.result_helper(rego.metadata.chain(), [key])
36+
}
37+
38+
# METADATA
39+
# title: Known Repository URLs
40+
# scope: rule
41+
# custom:
42+
# short_name: deny_unpermitted_urls
43+
# failure_msg: '%s'
44+
deny contains result if {
45+
some purl, msg in _repo_url_errors
46+
base := lib.result_helper(rego.metadata.chain(), [msg])
47+
result := object.union(base, {"term": purl})
48+
}
49+
50+
_repo_url_errors[purl] := msg if {
51+
some pkg in maven.packages
52+
purl := pkg.purl
53+
source := _get_effective_url(pkg.repository_url)
54+
not _url_is_permitted(source)
55+
msg := sprintf("Package %q (source: %q) is not in the permitted list", [purl, source])
56+
}
57+
58+
_get_effective_url(url) := url if {
59+
url != ""
60+
} else := "https://repo.maven.apache.org/maven2/"
61+
62+
_url_is_permitted(url) if {
63+
permitted := lib.rule_data("allowed_maven_repositories")
64+
url in permitted
65+
}
66+
67+
_rule_data_errors contains key if {
68+
key := "allowed_maven_repositories"
69+
data_list := object.get(data.rule_data, key, "UNDEFINED_IN_DATA")
70+
_is_invalid_data(data_list)
71+
}
72+
73+
_is_invalid_data(val) if val == "UNDEFINED_IN_DATA"
74+
75+
_is_invalid_data(val) if not is_array(val)
76+
77+
_is_invalid_data(val) if {
78+
is_array(val)
79+
count(val) == 0
80+
}

0 commit comments

Comments
 (0)