Skip to content

Commit 2d2903c

Browse files
authored
Merge pull request #1624 from joejstuart/volatile-config-warnings
Volatile config warnings
2 parents cc8b45a + ccf7556 commit 2d2903c

File tree

8 files changed

+1459
-1
lines changed

8 files changed

+1459
-1
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
= Volatile Configuration Warnings Package
2+
3+
This package generates warnings for volatile configuration rules that have lifecycle events requiring attention. Volatile config rules can include or exclude policy rules with time-based constraints (effectiveOn/effectiveUntil). Warnings help users proactively manage rule expirations and activations. The optional `reference` field in volatile criteria can be used to link to related documentation (e.g., Jira issues) but is not included in warnings.
4+
5+
== Package Name
6+
7+
* `volatile_config`
8+
9+
== Rules Included
10+
11+
[#volatile_config__expiring_rule]
12+
=== link:#volatile_config__expiring_rule[Volatile rule expiring soon]
13+
14+
Generates a warning when a volatile configuration rule will expire within the configured warning threshold (default 30 days). This provides advance notice to extend or replace the rule before it expires.
15+
16+
*Solution*: Review the volatile configuration rule and decide whether to extend its effectiveUntil date or remove it. If the rule is no longer needed, you can safely let it expire.
17+
18+
* Rule type: [rule-type-indicator warning]#WARNING#
19+
* WARNING message: `Volatile %s rule '%s' expires in %d days (effective until: %s)`
20+
* Code: `volatile_config.expiring_rule`
21+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/volatile_config/volatile_config.rego#L59[Source, window="_blank"]
22+
23+
[#volatile_config__expired_rule]
24+
=== link:#volatile_config__expired_rule[Volatile rule has expired]
25+
26+
Generates a warning when a volatile configuration rule has passed its effectiveUntil date. Expired rules are no longer active and should be removed from the policy configuration.
27+
28+
*Solution*: Remove the expired volatile configuration rule from your policy. The rule is no longer having any effect and keeping it may cause confusion.
29+
30+
* Rule type: [rule-type-indicator warning]#WARNING#
31+
* WARNING message: `Volatile %s rule '%s' has expired (effective until: %s)`
32+
* Code: `volatile_config.expired_rule`
33+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/volatile_config/volatile_config.rego#L112[Source, window="_blank"]
34+
35+
[#volatile_config__invalid_config]
36+
=== link:#volatile_config__invalid_config[Volatile rule has invalid configuration]
37+
38+
Generates a warning when a volatile configuration rule has invalid date values that cannot be parsed. This indicates a configuration error that should be corrected.
39+
40+
*Solution*: Correct the date format in the volatile configuration rule. Dates must be in RFC 3339 format (e.g., "2024-12-31T00:00:00Z").
41+
42+
* Rule type: [rule-type-indicator warning]#WARNING#
43+
* WARNING message: `Volatile %s rule '%s' has invalid date configuration (effectiveOn: %s, effectiveUntil: %s)`
44+
* Code: `volatile_config.invalid_config`
45+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/volatile_config/volatile_config.rego#L137[Source, window="_blank"]
46+
47+
[#volatile_config__no_expiration]
48+
=== link:#volatile_config__no_expiration[Volatile rule has no expiration]
49+
50+
Generates a warning when a volatile configuration rule has no effectiveUntil date set. Rules without expiration dates may accumulate over time and should be periodically reviewed.
51+
52+
*Solution*: Consider adding an effectiveUntil date to the volatile configuration rule to ensure it is reviewed periodically. Permanent exceptions should be documented with justification.
53+
54+
* Rule type: [rule-type-indicator warning]#WARNING#
55+
* WARNING message: `Volatile %s rule '%s' has no expiration date set`
56+
* Code: `volatile_config.no_expiration`
57+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/volatile_config/volatile_config.rego#L86[Source, window="_blank"]
58+
59+
[#volatile_config__pending_rule]
60+
=== link:#volatile_config__pending_rule[Volatile rule pending activation]
61+
62+
Generates a warning when a volatile configuration rule has an effectiveOn date in the future, indicating it will become active at that time.
63+
64+
*Solution*: This is informational. The volatile configuration rule will automatically become active on the effective date. No action is required unless you want to adjust the activation timing.
65+
66+
* Rule type: [rule-type-indicator warning]#WARNING#
67+
* WARNING message: `Volatile %s rule '%s' is pending activation (effective on: %s)`
68+
* Code: `volatile_config.pending_rule`
69+
* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/volatile_config/volatile_config.rego#L34[Source, window="_blank"]

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ Rules included:
5656
* xref:packages/release_slsa_source_correlated.adoc#slsa_source_correlated__attested_source_code_reference[SLSA - Verification model - Source: Source reference]
5757
* xref:packages/release_sbom_spdx.adoc#sbom_spdx__valid[SPDX SBOM: Valid]
5858
* xref:packages/release_tasks.adoc#tasks__pipeline_has_tasks[Tasks: Pipeline run includes at least one task]
59-
* xref:packages/release_tasks.adoc#tasks__successful_pipeline_tasks[Tasks: Successful pipeline tasks]
59+
* xref:packages/release_tasks.adoc#tasks__successful_pipeline_tasks[Tasks: Successful pipeline tasks]
60+
* xref:packages/release_volatile_config.adoc#volatile_config__expiring_rule[Volatile Configuration Warnings: Volatile rule expiring soon]
61+
* xref:packages/release_volatile_config.adoc#volatile_config__expired_rule[Volatile Configuration Warnings: Volatile rule has expired]
62+
* xref:packages/release_volatile_config.adoc#volatile_config__invalid_config[Volatile Configuration Warnings: Volatile rule has invalid configuration]
63+
* xref:packages/release_volatile_config.adoc#volatile_config__no_expiration[Volatile Configuration Warnings: Volatile rule has no expiration]
64+
* xref:packages/release_volatile_config.adoc#volatile_config__pending_rule[Volatile Configuration Warnings: Volatile rule pending activation]
6065

6166
| [#policy_data]`policy_data`
6267
a| Include policy rules responsible for validating rule data.
@@ -214,6 +219,11 @@ Rules included:
214219
* xref:packages/release_trusted_task.adoc#trusted_task__current[Trusted Task checks: Tasks using the latest versions]
215220
* xref:packages/release_trusted_task.adoc#trusted_task__valid_trusted_artifact_inputs[Trusted Task checks: Trusted Artifact produced in pipeline]
216221
* xref:packages/release_trusted_task.adoc#trusted_task__trusted_parameters[Trusted Task checks: Trusted parameters]
222+
* xref:packages/release_volatile_config.adoc#volatile_config__expiring_rule[Volatile Configuration Warnings: Volatile rule expiring soon]
223+
* xref:packages/release_volatile_config.adoc#volatile_config__expired_rule[Volatile Configuration Warnings: Volatile rule has expired]
224+
* xref:packages/release_volatile_config.adoc#volatile_config__invalid_config[Volatile Configuration Warnings: Volatile rule has invalid configuration]
225+
* xref:packages/release_volatile_config.adoc#volatile_config__no_expiration[Volatile Configuration Warnings: Volatile rule has no expiration]
226+
* xref:packages/release_volatile_config.adoc#volatile_config__pending_rule[Volatile Configuration Warnings: Volatile rule pending activation]
217227
* xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__builder_image_param[rpm-ostree Task: Builder image parameter]
218228
* xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__rule_data[rpm-ostree Task: Rule data]
219229

@@ -470,6 +480,9 @@ a| Conforma requires that each build was subjected to a set of tests and that th
470480
| xref:packages/release_trusted_task.adoc[trusted_task]
471481
a| This package is used to verify all the Tekton Tasks involved in building the image are trusted. Trust is established by comparing the Task references found in the SLSA Provenance with a pre-defined list of trusted Tasks, which is expected to be provided as a data source that creates the `data.trusted_tasks` in the format demonstrated at https://github.com/conforma/policy/blob/main/example/data/trusted_tekton_tasks.yml. The list can be extended or customized using the `trusted_tasks` rule data key which is merged into the `trusted_tasks` data.
472482

483+
| xref:packages/release_volatile_config.adoc[volatile_config]
484+
a| This package generates warnings for volatile configuration rules that have lifecycle events requiring attention. Volatile config rules can include or exclude policy rules with time-based constraints (effectiveOn/effectiveUntil). Warnings help users proactively manage rule expirations and activations. The optional `reference` field in volatile criteria can be used to link to related documentation (e.g., Jira issues) but is not included in warnings.
485+
473486
| xref:packages/release_rpm_ostree_task.adoc[rpm_ostree_task]
474487
a| This package is responsible for verifying the rpm-ostree Tekton Task was executed with the expected parameters.
475488

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@
177177
**** xref:packages/release_trusted_task.adoc#trusted_task__current[Tasks using the latest versions]
178178
**** xref:packages/release_trusted_task.adoc#trusted_task__valid_trusted_artifact_inputs[Trusted Artifact produced in pipeline]
179179
**** xref:packages/release_trusted_task.adoc#trusted_task__trusted_parameters[Trusted parameters]
180+
*** xref:packages/release_volatile_config.adoc[Volatile Configuration Warnings]
181+
**** xref:packages/release_volatile_config.adoc#volatile_config__expiring_rule[Volatile rule expiring soon]
182+
**** xref:packages/release_volatile_config.adoc#volatile_config__expired_rule[Volatile rule has expired]
183+
**** xref:packages/release_volatile_config.adoc#volatile_config__invalid_config[Volatile rule has invalid configuration]
184+
**** xref:packages/release_volatile_config.adoc#volatile_config__no_expiration[Volatile rule has no expiration]
185+
**** xref:packages/release_volatile_config.adoc#volatile_config__pending_rule[Volatile rule pending activation]
180186
*** xref:packages/release_rpm_ostree_task.adoc[rpm-ostree Task]
181187
**** xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__builder_image_param[Builder image parameter]
182188
**** xref:packages/release_rpm_ostree_task.adoc#rpm_ostree_task__rule_data[Rule data]

policy/lib/rule_data.rego

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ rule_data_defaults := {
132132
"trusted_tasks": {},
133133
# Number of days before a version of the Task expires that warnings are reported
134134
"task_expiry_warning_days": 0,
135+
# Number of days before a volatile config rule expires that warnings are reported
136+
# Used in release/volatile_config
137+
"volatile_config_warning_threshold_days": 30,
135138
# The gpg-pubkey RPM does not abide to the rule of a single RPM name being installed.
136139
"non_unique_rpm_names": ["gpg-pubkey"],
137140
}

policy/lib/volatile_config.rego

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# Copyright The Conforma Contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
# Library functions for evaluating volatile configuration rules and determining
18+
# warning categories based on lifecycle events (pending activation, expiring soon,
19+
# no expiration, expired, invalid dates).
20+
21+
package lib
22+
23+
import rego.v1
24+
25+
import data.lib.time as time_lib
26+
27+
# Get configurable warning threshold from rule_data (default defined in rule_data_defaults)
28+
warning_threshold_days := rule_data("volatile_config_warning_threshold_days")
29+
30+
# Nanoseconds per day constant
31+
_ns_per_day := 86400000000000
32+
33+
# Calculate days until a rule expires (returns integer days, can be negative if expired)
34+
days_until_expiration(rule) := days if {
35+
until_ns := _get_effective_until_ns(rule)
36+
now_ns := time_lib.effective_current_time_ns
37+
diff_ns := until_ns - now_ns
38+
days := floor(diff_ns / _ns_per_day)
39+
}
40+
41+
# Check if rule applies to current image/component
42+
# context is an object with optional fields: imageRef, imageDigest, componentName
43+
# Returns true if the rule matches based on any of the following criteria:
44+
# - Global rule (no image/component constraints)
45+
# - Match by imageRef (DEPRECATED: same as imageDigest, both are digests)
46+
# - Match by imageUrl prefix (URL without tag)
47+
# - Match by imageDigest
48+
# - Match by componentNames
49+
is_rule_applicable(rule, _) if {
50+
# Global rule: no constraints specified
51+
object.get(rule, "imageRef", "") == ""
52+
object.get(rule, "imageUrl", "") == ""
53+
object.get(rule, "imageDigest", "") == ""
54+
count(object.get(rule, "componentNames", [])) == 0
55+
}
56+
57+
is_rule_applicable(rule, context) if {
58+
# Match by imageRef (DEPRECATED: same as imageDigest, both are digests)
59+
rule_image_ref := object.get(rule, "imageRef", "")
60+
rule_image_ref != ""
61+
context_digest := object.get(context, "imageDigest", "")
62+
rule_image_ref == context_digest
63+
}
64+
65+
is_rule_applicable(rule, context) if {
66+
# Match by imageUrl prefix (URL without tag)
67+
rule_image_url := object.get(rule, "imageUrl", "")
68+
rule_image_url != ""
69+
context_image_ref := object.get(context, "imageRef", "")
70+
_image_url_matches(rule_image_url, context_image_ref)
71+
}
72+
73+
is_rule_applicable(rule, context) if {
74+
# Match by imageDigest
75+
rule_image_digest := object.get(rule, "imageDigest", "")
76+
rule_image_digest != ""
77+
context_digest := object.get(context, "imageDigest", "")
78+
rule_image_digest == context_digest
79+
}
80+
81+
is_rule_applicable(rule, context) if {
82+
# Match by componentNames
83+
component_names := object.get(rule, "componentNames", [])
84+
count(component_names) > 0
85+
context_component_name := object.get(context, "componentName", "")
86+
some name in component_names
87+
name == context_component_name
88+
}
89+
90+
# Determine warning category - check for invalid dates first
91+
warning_category(rule) := "invalid" if {
92+
_is_date_invalid(_get_effective_on(rule))
93+
}
94+
95+
warning_category(rule) := "invalid" if {
96+
_is_date_invalid(_get_effective_until(rule))
97+
}
98+
99+
# Pending: effectiveOn is in the future
100+
warning_category(rule) := "pending" if {
101+
_is_effective_on_in_future(rule)
102+
_is_effective_until_valid_or_empty(rule)
103+
}
104+
105+
# Expired: effectiveUntil is in the past
106+
warning_category(rule) := "expired" if {
107+
_is_effective_until_expired(rule)
108+
_is_effective_on_valid_and_not_future(rule)
109+
}
110+
111+
# Expiring: effectiveUntil is within the warning threshold
112+
warning_category(rule) := "expiring" if {
113+
_is_effective_until_expiring(rule)
114+
_is_effective_on_valid_and_not_future(rule)
115+
}
116+
117+
# No expiration: rule is active (effectiveOn in past or not set) but has no effectiveUntil
118+
warning_category(rule) := "no_expiration" if {
119+
_get_effective_until(rule) == ""
120+
_is_effective_on_active_or_unset(rule)
121+
}
122+
123+
# =============================================================================
124+
# Helper functions for date extraction and validation
125+
# =============================================================================
126+
127+
# Extract effectiveOn date string from rule
128+
_get_effective_on(rule) := object.get(rule, "effectiveOn", "")
129+
130+
# Extract effectiveUntil date string from rule
131+
_get_effective_until(rule) := object.get(rule, "effectiveUntil", "")
132+
133+
# Safely parse RFC3339 date, undefined on failure
134+
_parse_date_safe(date_str) := ns if {
135+
date_str != ""
136+
ns := time.parse_rfc3339_ns(date_str)
137+
}
138+
139+
# Check if a date string is invalid (non-empty but unparseable)
140+
# Empty strings are considered valid (not set, not invalid)
141+
_is_date_invalid(date_str) if {
142+
date_str != ""
143+
not _parse_date_safe(date_str)
144+
}
145+
146+
# Get effectiveOn as nanoseconds, undefined if invalid or empty
147+
_get_effective_on_ns(rule) := _parse_date_safe(_get_effective_on(rule))
148+
149+
# Get effectiveUntil as nanoseconds, undefined if invalid or empty
150+
_get_effective_until_ns(rule) := _parse_date_safe(_get_effective_until(rule))
151+
152+
# Check if effectiveOn is in the future
153+
_is_effective_on_in_future(rule) if {
154+
on_ns := _get_effective_on_ns(rule)
155+
now_ns := time_lib.effective_current_time_ns
156+
on_ns > now_ns
157+
}
158+
159+
# Check if effectiveOn is active (in the past) or not set
160+
_is_effective_on_active_or_unset(rule) if {
161+
_get_effective_on(rule) == ""
162+
} else if {
163+
on_ns := _get_effective_on_ns(rule)
164+
now_ns := time_lib.effective_current_time_ns
165+
on_ns <= now_ns
166+
}
167+
168+
# Check if effectiveOn is valid (if set) and not in the future
169+
_is_effective_on_valid_and_not_future(rule) if {
170+
_get_effective_on(rule) == ""
171+
} else if {
172+
on_ns := _get_effective_on_ns(rule)
173+
now_ns := time_lib.effective_current_time_ns
174+
on_ns <= now_ns
175+
}
176+
177+
# Check if effectiveUntil is valid (if set) or empty
178+
_is_effective_until_valid_or_empty(rule) if {
179+
_get_effective_until(rule) == ""
180+
} else if {
181+
_get_effective_until_ns(rule)
182+
}
183+
184+
# Check if effectiveUntil is expired (in the past)
185+
_is_effective_until_expired(rule) if {
186+
until_ns := _get_effective_until_ns(rule)
187+
now_ns := time_lib.effective_current_time_ns
188+
until_ns < now_ns
189+
}
190+
191+
# Check if effectiveUntil is expiring (within warning threshold)
192+
_is_effective_until_expiring(rule) if {
193+
until_ns := _get_effective_until_ns(rule)
194+
now_ns := time_lib.effective_current_time_ns
195+
until_ns >= now_ns
196+
days := days_until_expiration(rule)
197+
days <= warning_threshold_days
198+
}
199+
200+
# Helper: check if imageUrl matches the image reference
201+
# imageUrl is a URL pattern without tag (e.g., "quay.io/redhat/myimage")
202+
# image_ref may include tag and/or digest (e.g., "quay.io/redhat/myimage:v1@sha256:...")
203+
_image_url_matches(url_pattern, image_ref) if {
204+
# Extract the repo portion (before any : or @)
205+
ref_without_digest := split(image_ref, "@")[0]
206+
ref_without_tag := split(ref_without_digest, ":")[0]
207+
208+
# Check if pattern matches exactly
209+
ref_without_tag == url_pattern
210+
}
211+
212+
_image_url_matches(url_pattern, image_ref) if {
213+
ref_without_digest := split(image_ref, "@")[0]
214+
ref_without_tag := split(ref_without_digest, ":")[0]
215+
216+
# Also allow prefix matching for broader scopes (e.g., "quay.io/redhat" matches "quay.io/redhat/myimage")
217+
startswith(ref_without_tag, sprintf("%s/", [url_pattern]))
218+
}

0 commit comments

Comments
 (0)