From f176d7a88a77090c3a3cf8fcfb38f880042c4557 Mon Sep 17 00:00:00 2001 From: jstuart Date: Wed, 7 Jan 2026 15:35:02 -0600 Subject: [PATCH] Get image manifests in parallel The versions check in the trusted task rules pulls each task bundle in the attestation which takes too much time. This adds a built-in that pulls all task bundles in parallel. https://issues.redhat.com/browse/EC-1600 Assisted-by: Claude-4.5-opus --- policy/lib/tekton/trusted.rego | 81 +++++---- policy/lib/tekton/trusted_test.rego | 164 ++++++++++-------- .../required_tasks/required_tasks.rego | 19 +- policy/pipeline/task_bundle/task_bundle.rego | 19 +- policy/release/lib/attestations.rego | 8 + .../slsa_build_scripted_build.rego | 5 +- policy/release/tasks/tasks.rego | 5 +- policy/release/trusted_task/trusted_task.rego | 17 +- 8 files changed, 204 insertions(+), 114 deletions(-) diff --git a/policy/lib/tekton/trusted.rego b/policy/lib/tekton/trusted.rego index faacd25ed..56152d279 100644 --- a/policy/lib/tekton/trusted.rego +++ b/policy/lib/tekton/trusted.rego @@ -84,12 +84,13 @@ missing_all_trusted_tasks_data if { # Returns true if the task uses a trusted Task reference. # Routes to the appropriate system based on data presence. -is_trusted_task(task) if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +is_trusted_task(task, manifests) if { not missing_trusted_task_rules_data - is_trusted_task_rules(task) + is_trusted_task_rules(task, manifests) } -is_trusted_task(task) if { +is_trusted_task(task, manifests) if { missing_trusted_task_rules_data not missing_trusted_tasks_data is_trusted_task_legacy(task) @@ -97,9 +98,10 @@ is_trusted_task(task) if { # Returns a subset of tasks that do not use a trusted Task reference. # Routes to the appropriate system based on data presence. -untrusted_task_refs(tasks) := result if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +untrusted_task_refs(tasks, manifests) := result if { not missing_trusted_task_rules_data - result := untrusted_task_refs_rules(tasks) + result := untrusted_task_refs_rules(tasks, manifests) } else := result if { result := untrusted_task_refs_legacy(tasks) } @@ -111,19 +113,21 @@ untrusted_task_refs(tasks) := result if { # ============================================================================= # Returns a subset of tasks that are untrusted according to trusted_task_rules. -untrusted_task_refs_rules(tasks) := {task | +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +untrusted_task_refs_rules(tasks, manifests) := {task | some task in tasks - not is_trusted_task_rules(task) + not is_trusted_task_rules(task, manifests) } # Returns true if the task uses a trusted Task reference according to trusted_task_rules. # 1. If task matches a deny rule, it's not trusted # 2. If task matches an allow rule, it's trusted # 3. Otherwise, it's not trusted -is_trusted_task_rules(task) if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +is_trusted_task_rules(task, manifests) if { ref := task_ref(task) - not _task_matches_deny_rule(ref) - _task_matches_allow_rule(ref) + not _task_matches_deny_rule(ref, manifests) + _task_matches_allow_rule(ref, manifests) } # Merging in the trusted_task_rules rule data @@ -279,12 +283,13 @@ _rule_is_future(rule) if { } # Returns future deny rules that would match the given task -future_deny_rules_for_task(task) := matching_rules if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +future_deny_rules_for_task(task, manifests) := matching_rules if { ref := task_ref(task) matching_rules := [rule | some rule in future_deny_rules _pattern_matches(ref.key, rule.pattern) - _version_satisfies_any_rule_constraints(ref, rule) + _version_satisfies_any_rule_constraints(ref, rule, manifests) ] } @@ -297,19 +302,21 @@ _rule_is_effective(rule) if { } # Returns true if the task reference matches a deny rule pattern and version constraints (if specified) -_task_matches_deny_rule(ref) if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +_task_matches_deny_rule(ref, manifests) if { some rule in _effective_deny_rules _pattern_matches(ref.key, rule.pattern) - _version_satisfies_any_rule_constraints(ref, rule) + _version_satisfies_any_rule_constraints(ref, rule, manifests) } # Returns a list of patterns from deny rules that match the task, or an empty list if no deny rules match. # This only applies to trusted_task_rules (not legacy trusted_tasks). -denying_pattern(task) := [rule.pattern | +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +denying_pattern(task, manifests) := [rule.pattern | ref := task_ref(task) some rule in _effective_deny_rules _pattern_matches(ref.key, rule.pattern) - _version_satisfies_any_rule_constraints(ref, rule) + _version_satisfies_any_rule_constraints(ref, rule, manifests) ] # Returns the reason why a task reference was denied, or nothing if the task is trusted. @@ -319,8 +326,9 @@ denying_pattern(task) := [rule.pattern | # 2. It doesn't match any allow rule pattern (type: "not_allowed", pattern: empty list) # 3. No effective allow rules exist but raw rules are defined (type: "no_effective_rules") # This only applies to trusted_task_rules (not legacy trusted_tasks). -denial_reason(task) := reason if { - deny_info := _denying_rules_info(task) +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +denial_reason(task, manifests) := reason if { + deny_info := _denying_rules_info(task, manifests) count(deny_info.patterns) > 0 reason := { "type": "deny_rule", @@ -332,8 +340,8 @@ denial_reason(task) := reason if { # Only applies if there are effective allow rules defined ref := task_ref(task) count(_effective_allow_rules) > 0 - not _task_matches_allow_rule(ref) - not _task_matches_deny_rule(ref) + not _task_matches_allow_rule(ref, manifests) + not _task_matches_deny_rule(ref, manifests) reason := { "type": "not_allowed", @@ -354,14 +362,15 @@ denial_reason(task) := reason if { } # Returns patterns and messages from deny rules that match the task -_denying_rules_info(task) := {"patterns": patterns, "messages": messages} if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +_denying_rules_info(task, manifests) := {"patterns": patterns, "messages": messages} if { ref := task_ref(task) # Get all matching deny rules matching_rules := [rule | some rule in _effective_deny_rules _pattern_matches(ref.key, rule.pattern) - _version_satisfies_any_rule_constraints(ref, rule) + _version_satisfies_any_rule_constraints(ref, rule, manifests) ] patterns := [rule.pattern | some rule in matching_rules] @@ -369,10 +378,11 @@ _denying_rules_info(task) := {"patterns": patterns, "messages": messages} if { } # Returns true if the task reference matches an allow rule pattern and version constraints (if specified) -_task_matches_allow_rule(ref) if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +_task_matches_allow_rule(ref, manifests) if { some rule in _effective_allow_rules _pattern_matches(ref.key, rule.pattern) - _version_satisfies_all_rule_constraints(ref, rule) + _version_satisfies_all_rule_constraints(ref, rule, manifests) } # Converts a wildcard pattern to a regex pattern and checks if the key matches @@ -478,11 +488,12 @@ _trusted_task_rules_schema := { # Returns false if versions field exists but no manifest version is found (don't allow by default for security) # Returns true if task version satisfies all constraints # Returns false otherwise -_version_satisfies_all_rule_constraints(ref, rule) if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +_version_satisfies_all_rule_constraints(ref, rule, manifests) if { not "versions" in object.keys(rule) } else if { # If versions field exists, manifest version must be found - manifest_version := _get_manifest_version_annotation(ref) + manifest_version := _get_manifest_version_annotation(ref, manifests) version := _normalize_version(manifest_version) semver.is_valid(version) @@ -505,18 +516,19 @@ _version_satisfies_all_rule_constraints(ref, rule) if { # Returns true if versions field exists but no manifest version is found (deny by default for security) # Returns true if task version satisfies at least one constraint # Returns false otherwise -_version_satisfies_any_rule_constraints(ref, rule) if { +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +_version_satisfies_any_rule_constraints(ref, rule, manifests) if { not "versions" in object.keys(rule) } else if { # If versions field exists but no manifest version found, deny the task (return true) "versions" in object.keys(rule) - not _get_manifest_version_annotation(ref) + not _get_manifest_version_annotation(ref, manifests) } else if { - manifest_version := _get_manifest_version_annotation(ref) + manifest_version := _get_manifest_version_annotation(ref, manifests) version := _normalize_version(manifest_version) not semver.is_valid(version) } else if { - manifest_version := _get_manifest_version_annotation(ref) + manifest_version := _get_manifest_version_annotation(ref, manifests) version := _normalize_version(manifest_version) semver.is_valid(version) @@ -572,11 +584,10 @@ _result_satisfies_operator(result, constraint) if { result < 0 } else := false -_get_manifest_version_annotation(ref) := version if { - # Only attempt to fetch manifest for bundle references - bundle_ref := object.get(ref, "bundle", "") - bundle_ref != "" - task_manifest := ec.oci.image_manifest(bundle_ref) +# Returns the version annotation from the manifest for a bundle reference. +# manifests is a map of bundle_ref -> manifest from ec.oci.image_manifests +_get_manifest_version_annotation(ref, manifests) := version if { + task_manifest := manifests[ref.bundle] annotations := object.get(task_manifest, "annotations", {}) version := annotations["org.opencontainers.image.version"] version != null diff --git a/policy/lib/tekton/trusted_test.rego b/policy/lib/tekton/trusted_test.rego index 1239c2d08..6ef2dcb53 100644 --- a/policy/lib/tekton/trusted_test.rego +++ b/policy/lib/tekton/trusted_test.rego @@ -22,6 +22,14 @@ import data.lib.time as time_lib # # ############################################################################# +# Helper to create a mock manifests map for testing +# bundle_ref: the bundle reference (e.g., "registry.local/trusty:1.0@sha256:digest") +# version: the version annotation value (e.g., "1.0") +_mock_manifests(bundle_ref, version) := {bundle_ref: {"annotations": {"org.opencontainers.image.version": version}}} + +# Empty manifests for tests that don't need version checking +_empty_manifests := {} + # ============================================================================= # SHARED HELPERS TESTS # ============================================================================= @@ -151,7 +159,7 @@ test_untrusted_task_refs if { expected := {untrusted_bundle_task, expired_trusted_bundle_task, untrusted_git_task, expired_trusted_git_task} - lib.assert_equal(expected, tekton.untrusted_task_refs(tasks)) with data.trusted_tasks as trusted_tasks + lib.assert_equal(expected, tekton.untrusted_task_refs(tasks, _empty_manifests)) with data.trusted_tasks as trusted_tasks } # Test untrusted_task_refs routing to rules system when trusted_task_rules data is present @@ -167,17 +175,17 @@ test_untrusted_task_refs_routes_to_rules if { # untrusted_bundle_task should be untrusted (doesn't match allow pattern) expected := {untrusted_bundle_task} - lib.assert_equal(expected, tekton.untrusted_task_refs(tasks)) with data.rule_data.trusted_task_rules as task_rules + lib.assert_equal(expected, tekton.untrusted_task_refs(tasks, _empty_manifests)) with data.rule_data.trusted_task_rules as task_rules } test_is_trusted_task if { - tekton.is_trusted_task(trusted_bundle_task) with data.trusted_tasks as trusted_tasks - tekton.is_trusted_task(trusted_git_task) with data.trusted_tasks as trusted_tasks + tekton.is_trusted_task(trusted_bundle_task, _empty_manifests) with data.trusted_tasks as trusted_tasks + tekton.is_trusted_task(trusted_git_task, _empty_manifests) with data.trusted_tasks as trusted_tasks - not tekton.is_trusted_task(untrusted_bundle_task) with data.trusted_tasks as trusted_tasks - not tekton.is_trusted_task(untrusted_git_task) with data.trusted_tasks as trusted_tasks - not tekton.is_trusted_task(expired_trusted_git_task) with data.trusted_tasks as trusted_tasks - not tekton.is_trusted_task(expired_trusted_bundle_task) with data.trusted_tasks as trusted_tasks + not tekton.is_trusted_task(untrusted_bundle_task, _empty_manifests) with data.trusted_tasks as trusted_tasks + not tekton.is_trusted_task(untrusted_git_task, _empty_manifests) with data.trusted_tasks as trusted_tasks + not tekton.is_trusted_task(expired_trusted_git_task, _empty_manifests) with data.trusted_tasks as trusted_tasks + not tekton.is_trusted_task(expired_trusted_bundle_task, _empty_manifests) with data.trusted_tasks as trusted_tasks } # ============================================================================= @@ -222,8 +230,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "task-something"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(allowed_task) with data.rule_data.trusted_task_rules as trusted_task_rules - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "0.4"}} + allowed_task_manifests := _mock_manifests("quay.io/konflux-ci/tekton-catalog/task-something:0.4@sha256:digest", "0.4") + tekton.is_trusted_task(allowed_task, allowed_task_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # Task that matches deny rule should not be trusted (deny takes precedence) denied_task := {"spec": {"taskRef": {"resolver": "bundles", "params": [ @@ -231,8 +239,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "task-buildah"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(denied_task) with data.rule_data.trusted_task_rules as trusted_task_rules - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "0.3"}} + denied_task_manifests := _mock_manifests("quay.io/konflux-ci/tekton-catalog/task-buildah:0.3@sha256:digest", "0.3") + not tekton.is_trusted_task(denied_task, denied_task_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # Task that matches allow pattern (registry.local) should be trusted # Note: The key format is oci://registry.local/trusty:1.0 (with tag), so pattern oci://registry.local/* matches @@ -241,8 +249,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "trusty"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(registry_local_task) with data.rule_data.trusted_task_rules as trusted_task_rules - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.0"}} + registry_local_manifests := _mock_manifests("registry.local/trusty:1.0@sha256:digest", "1.0") + tekton.is_trusted_task(registry_local_task, registry_local_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # Task that doesn't match any allow rule should not be trusted # Note: This task uses a different path (untrusted) that doesn't match the pattern @@ -251,8 +259,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "untrusted"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(not_allowed_task) with data.rule_data.trusted_task_rules as trusted_task_rules - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.0"}} + not_allowed_manifests := _mock_manifests("other-registry.io/untrusted:1.0@sha256:digest", "1.0") + not tekton.is_trusted_task(not_allowed_task, not_allowed_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # Tasks satisfying at least one deny rule version constraints should be denied deny_constrained_task_denied_version := {"spec": {"taskRef": {"resolver": "bundles", "params": [ @@ -260,8 +268,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "constrained"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(deny_constrained_task_denied_version) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.5"}} + deny_constrained_denied_manifests := _mock_manifests("quay.io/konflux-ci/tekton-catalog/deny-task-constrained:1.5@sha256:digest", "1.5") + not tekton.is_trusted_task(deny_constrained_task_denied_version, deny_constrained_denied_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length # Task not satisfying any deny rule version constraints should not be denied deny_constrained_task_valid_version := {"spec": {"taskRef": {"resolver": "bundles", "params": [ @@ -269,8 +277,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "constrained"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(deny_constrained_task_valid_version) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.2.3"}} + deny_constrained_valid_manifests := _mock_manifests("quay.io/konflux-ci/tekton-catalog/deny-task-constrained:1.2.3@sha256:digest", "1.2.3") + tekton.is_trusted_task(deny_constrained_task_valid_version, deny_constrained_valid_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length # Tasks satisfying all the allow-rule version constraints should be allowed allow_constrained_task_valid_version := {"spec": {"taskRef": {"resolver": "bundles", "params": [ @@ -278,8 +286,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "constrained"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(allow_constrained_task_valid_version) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.5"}} + allow_constrained_valid_manifests := _mock_manifests("quay.io/konflux-ci/another-catalog/allow-task-constrained:1.5@sha256:digest", "1.5") + tekton.is_trusted_task(allow_constrained_task_valid_version, allow_constrained_valid_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length # Tasks *NOT* satisfying all the allow-rule version constraints should be denied allow_constrained_task_denied_version := {"spec": {"taskRef": {"resolver": "bundles", "params": [ @@ -287,8 +295,8 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "constrained"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(allow_constrained_task_denied_version) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.2.3"}} + allow_constrained_denied_manifests := _mock_manifests("quay.io/konflux-ci/another-catalog/allow-task-constrained:1.2.3@sha256:digest", "1.2.3") + not tekton.is_trusted_task(allow_constrained_task_denied_version, allow_constrained_denied_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length # Task with mismatching versions between ref and manifest annotations. # Only the manifest annotation is taken into consideration @@ -297,8 +305,10 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "constrained"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(allow_constrained_task_denied_version_mismatching_1) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.2.3"}} + + # Manifest has 1.2.3 even though ref has 1.5 - manifest version is used + mismatch_manifests_1 := _mock_manifests("quay.io/konflux-ci/another-catalog/allow-task-constrained:1.5@sha256:digest", "1.2.3") + not tekton.is_trusted_task(allow_constrained_task_denied_version_mismatching_1, mismatch_manifests_1) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length # Task with mismatching versions between ref and manifest annotations. # Only the manifest annotation is taken into consideration @@ -307,8 +317,10 @@ test_is_trusted_task_with_rules if { {"name": "name", "value": "constrained"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(allow_constrained_task_denied_version_mismatching_2) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length - with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.5"}} + + # Manifest has 1.5 even though ref has 1.2.3 - manifest version is used + mismatch_manifests_2 := _mock_manifests("quay.io/konflux-ci/another-catalog/allow-task-constrained:1.2.3@sha256:digest", "1.5") + tekton.is_trusted_task(allow_constrained_task_denied_version_mismatching_2, mismatch_manifests_2) with data.rule_data.trusted_task_rules as trusted_task_rules # regal ignore:line-length } test_trusted_task_records if { @@ -362,7 +374,7 @@ test_data_trusted_task_rules_extraction if { {"name": "name", "value": "trusty"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(allowed_task) with data.trusted_task_rules as data_rules_allow + tekton.is_trusted_task(allowed_task, _empty_manifests) with data.trusted_task_rules as data_rules_allow with data.rule_data.trusted_task_rules as null # Test when data.trusted_task_rules is provided with deny rules @@ -377,12 +389,12 @@ test_data_trusted_task_rules_extraction if { {"name": "name", "value": "crook"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(denied_task) with data.trusted_task_rules as data_rules_deny + not tekton.is_trusted_task(denied_task, _empty_manifests) with data.trusted_task_rules as data_rules_deny with data.rule_data.trusted_task_rules as null # Test when data.trusted_task_rules is not provided (covers default cases 145, 152) # Should fall back to empty arrays, so task won't be trusted via rules - not tekton.is_trusted_task(allowed_task) with data.trusted_task_rules as null + not tekton.is_trusted_task(allowed_task, _empty_manifests) with data.trusted_task_rules as null with data.rule_data.trusted_task_rules as null } @@ -406,7 +418,7 @@ test_rule_data_trusted_task_rules_extraction if { {"name": "name", "value": "task-something"}, {"name": "kind", "value": "task"}, ]}}} - tekton.is_trusted_task(allowed_task) with data.rule_data.trusted_task_rules as rule_data_rules + tekton.is_trusted_task(allowed_task, _empty_manifests) with data.rule_data.trusted_task_rules as rule_data_rules # Task matching deny from rule_data should not be trusted denied_task := {"spec": {"taskRef": {"resolver": "bundles", "params": [ @@ -414,11 +426,11 @@ test_rule_data_trusted_task_rules_extraction if { {"name": "name", "value": "task-buildah"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.is_trusted_task(denied_task) with data.rule_data.trusted_task_rules as rule_data_rules + not tekton.is_trusted_task(denied_task, _empty_manifests) with data.rule_data.trusted_task_rules as rule_data_rules # Test when lib_rule_data returns [] (not an object) - covers default cases # When rule_data returns [], it's not an object, so defaults are used - not tekton.is_trusted_task(allowed_task) with data.rule_data.trusted_task_rules as [] + not tekton.is_trusted_task(allowed_task, _empty_manifests) with data.rule_data.trusted_task_rules as [] } test_data_errors if { @@ -506,7 +518,7 @@ test_denying_pattern if { ]}}} # Should return a list with the pattern that denied it - patterns := tekton.denying_pattern(denied_task) with data.rule_data.trusted_task_rules as trusted_task_rules + patterns := tekton.denying_pattern(denied_task, _empty_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules lib.assert_equal(["oci://quay.io/konflux-ci/tekton-catalog/task-buildah*"], patterns) # Task that doesn't match any deny rule should return empty list @@ -517,7 +529,7 @@ test_denying_pattern if { ]}}} # regal ignore:line-length - patterns_empty := tekton.denying_pattern(non_matching_task) with data.rule_data.trusted_task_rules as trusted_task_rules + patterns_empty := tekton.denying_pattern(non_matching_task, _empty_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules lib.assert_equal([], patterns_empty) } @@ -543,7 +555,7 @@ test_denying_pattern_multiple_rules if { {"name": "name", "value": "task-buildah"}, {"name": "kind", "value": "task"}, ]}}} - patterns_multi := tekton.denying_pattern(buildah_task) with data.rule_data.trusted_task_rules as multiple_deny_rules + patterns_multi := tekton.denying_pattern(buildah_task, _empty_manifests) with data.rule_data.trusted_task_rules as multiple_deny_rules # Should contain both patterns (order may vary) lib.assert_equal(2, count(patterns_multi)) @@ -575,7 +587,7 @@ test_denial_reason if { {"name": "name", "value": "task-buildah"}, {"name": "kind", "value": "task"}, ]}}} - reason_deny := tekton.denial_reason(denied_task) with data.rule_data.trusted_task_rules as trusted_task_rules + reason_deny := tekton.denial_reason(denied_task, _empty_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules lib.assert_equal("deny_rule", reason_deny.type) lib.assert_equal(["oci://quay.io/konflux-ci/tekton-catalog/task-buildah*"], reason_deny.pattern) lib.assert_equal(["This version is deprecated"], reason_deny.messages) @@ -588,7 +600,7 @@ test_denial_reason if { ]}}} # regal ignore:line-length - reason_not_allowed := tekton.denial_reason(not_allowed_task) with data.rule_data.trusted_task_rules as trusted_task_rules + reason_not_allowed := tekton.denial_reason(not_allowed_task, _empty_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules lib.assert_equal("not_allowed", reason_not_allowed.type) lib.assert_equal([], reason_not_allowed.pattern) lib.assert_equal([], reason_not_allowed.messages) @@ -599,11 +611,11 @@ test_denial_reason if { {"name": "name", "value": "task-something"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.denial_reason(allowed_task) with data.rule_data.trusted_task_rules as trusted_task_rules + not tekton.denial_reason(allowed_task, _empty_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules # Task in legacy trusted_tasks but doesn't match allow rules should return "not_allowed" # (denial_reason only works with trusted_task_rules, not legacy) - reason_legacy := tekton.denial_reason(trusted_bundle_task) with data.rule_data.trusted_task_rules as trusted_task_rules + reason_legacy := tekton.denial_reason(trusted_bundle_task, _empty_manifests) with data.rule_data.trusted_task_rules as trusted_task_rules with data.trusted_tasks as trusted_tasks lib.assert_equal("not_allowed", reason_legacy.type) lib.assert_equal([], reason_legacy.pattern) @@ -624,7 +636,7 @@ test_denial_reason_no_allow_rules if { {"name": "name", "value": "untrusted"}, {"name": "kind", "value": "task"}, ]}}} - not tekton.denial_reason(untrusted_task) with data.rule_data.trusted_task_rules as rules_no_allow + not tekton.denial_reason(untrusted_task, _empty_manifests) with data.rule_data.trusted_task_rules as rules_no_allow } test_trusted_task_rules_data_errors if { @@ -714,7 +726,7 @@ test_denying_pattern_invalid_task if { } # Should return empty list (else branch) since task_ref fails - patterns := tekton.denying_pattern(invalid_task) with data.rule_data.trusted_task_rules as rules + patterns := tekton.denying_pattern(invalid_task, _empty_manifests) with data.rule_data.trusted_task_rules as rules lib.assert_equal([], patterns) } @@ -738,10 +750,10 @@ test_denying_rules_info_empty if { ]}}} # denial_reason returns nothing for allowed tasks (internally calls _denying_rules_info which returns empty) - not tekton.denial_reason(allowed_task) with data.rule_data.trusted_task_rules as rules_no_deny + not tekton.denial_reason(allowed_task, _empty_manifests) with data.rule_data.trusted_task_rules as rules_no_deny # denying_pattern should also return empty list (covers line 337) - patterns := tekton.denying_pattern(allowed_task) with data.rule_data.trusted_task_rules as rules_no_deny + patterns := tekton.denying_pattern(allowed_task, _empty_manifests) with data.rule_data.trusted_task_rules as rules_no_deny lib.assert_equal([], patterns) } @@ -890,47 +902,63 @@ unsorted_trusted_task := {"oci://registry.local/trusty:1.0": [ test_version_satisfies_all_rule_constraints if { # No version constraints in rule - should always pass - tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.2.3"}, {}) + tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.2.3"}, {}, _empty_manifests) # Has version constraints and valid semver - tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.2.3"}} # regal ignore:line-length - tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "1.1.0"}} # regal ignore:line-length - tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.1.1"}} # regal ignore:line-length - tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<=3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v3.0.0"}} # regal ignore:line-length + manifests_1_2_3 := _mock_manifests("example.com/task:1.0", "1.2.3") + tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}, manifests_1_2_3) + manifests_1_1_0 := _mock_manifests("example.com/task:1.0", "1.1.0") + tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}, manifests_1_1_0) + manifests_1_1_1 := _mock_manifests("example.com/task:1.0", "v1.1.1") + tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}, manifests_1_1_1) + manifests_3_0_0 := _mock_manifests("example.com/task:1.0", "v3.0.0") + tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<=3"]}, manifests_3_0_0) # Version doesn't match all the constraints - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.5.0"}} # regal ignore:line-length - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.1.0"}} # regal ignore:line-length - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v3.0.0"}} # regal ignore:line-length - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": ["<2", ">=1.5.1"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.5.0"}} # regal ignore:line-length + manifests_1_5_0 := _mock_manifests("example.com/task:1.0", "v1.5.0") + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}, manifests_1_5_0) + manifests_v1_1_0 := _mock_manifests("example.com/task:1.0", "v1.1.0") + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}, manifests_v1_1_0) + manifests_v3_0_0 := _mock_manifests("example.com/task:1.0", "v3.0.0") + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}, manifests_v3_0_0) + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": ["<2", ">=1.5.1"]}, manifests_1_5_0) # Invalid inputs - should fail - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}) with ec.oci.image_manifest as {"annotations": {}} # regal ignore:line-length - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "invalid"}} # regal ignore:line-length + manifests_empty := {"example.com/task:1.0": {"annotations": {}}} + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}, manifests_empty) + manifests_invalid := _mock_manifests("example.com/task:1.0", "invalid") + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}, manifests_invalid) } test_version_satisfies_any_rule_constraints if { # No version constraints in rule - should always pass - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.2.3"}, {}) + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.2.3"}, {}, _empty_manifests) # Has version constraints and valid semver - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.2.3"}} # regal ignore:line-length - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.1.0"}} # regal ignore:line-length - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.1.1"}} # regal ignore:line-length - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<=3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v3.0.0"}} # regal ignore:line-length + manifests_v1_2_3 := _mock_manifests("example.com/task:1.0", "v1.2.3") + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}, manifests_v1_2_3) + manifests_v1_1_0 := _mock_manifests("example.com/task:1.0", "v1.1.0") + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=1.1", "<3"]}, manifests_v1_1_0) + manifests_v1_1_1 := _mock_manifests("example.com/task:1.0", "v1.1.1") + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}, manifests_v1_1_1) + manifests_v3_0_0 := _mock_manifests("example.com/task:1.0", "v3.0.0") + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<=3"]}, manifests_v3_0_0) # Version doesn't match all the constraints, but still passes - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.1.0"}} # regal ignore:line-length - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v3.0.0"}} # regal ignore:line-length + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}, manifests_v1_1_0) + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">1.1", "<3"]}, manifests_v3_0_0) # Version doesn't match any constraint - not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.5.0"}} # regal ignore:line-length - not tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": ["<1", ">=1.5.1"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.5.0"}} # regal ignore:line-length + manifests_v1_5_0 := _mock_manifests("example.com/task:1.0", "v1.5.0") + not tekton._version_satisfies_all_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}, manifests_v1_5_0) + not tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": ["<1", ">=1.5.1"]}, manifests_v1_5_0) # Missing or invalid version annotation - should return true (deny by default for security) - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}) with ec.oci.image_manifest as {"annotations": {}} # regal ignore:line-length - tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "invalid"}} # regal ignore:line-length - not tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": ["<1", ">=1.5.1"]}) with ec.oci.image_manifest as {"annotations": {"org.opencontainers.image.version": "v1.5.0"}} # regal ignore:line-length + manifests_empty := {"example.com/task:1.0": {"annotations": {}}} + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}, manifests_empty) + manifests_invalid := _mock_manifests("example.com/task:1.0", "invalid") + tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": [">=2"]}, manifests_invalid) + not tekton._version_satisfies_any_rule_constraints({"bundle": "example.com/task:1.0"}, {"versions": ["<1", ">=1.5.1"]}, manifests_v1_5_0) } test_normalize_version if { diff --git a/policy/pipeline/required_tasks/required_tasks.rego b/policy/pipeline/required_tasks/required_tasks.rego index bc434453e..a7056c0f3 100644 --- a/policy/pipeline/required_tasks/required_tasks.rego +++ b/policy/pipeline/required_tasks/required_tasks.rego @@ -13,6 +13,23 @@ import rego.v1 import data.lib import data.lib.tekton +# ============================================================================= +# MANIFEST PRE-FETCHING +# Collect all bundle references and fetch manifests in a single batch call. +# ============================================================================= + +# Collect all unique bundle references from tasks in the pipeline definition +_all_bundle_refs := {tekton.task_ref(task).bundle | + some task in tekton.tasks(input) + tekton.task_ref(task).bundle != "" +} + +# Batch fetch all manifests at once using ec.oci.image_manifests +# This returns a map of bundle_ref -> manifest +_manifests := ec.oci.image_manifests(_all_bundle_refs) + +# ============================================================================= + # METADATA # title: Required tasks found in pipeline definition # description: >- @@ -107,7 +124,7 @@ deny contains result if { _missing_tasks(required_tasks) := {task | trusted := [task_name | some task in tekton.tasks(input) - tekton.is_trusted_task(task) + tekton.is_trusted_task(task, _manifests) some task_name in tekton.task_names(task) ] diff --git a/policy/pipeline/task_bundle/task_bundle.rego b/policy/pipeline/task_bundle/task_bundle.rego index fdce13139..0207115cb 100644 --- a/policy/pipeline/task_bundle/task_bundle.rego +++ b/policy/pipeline/task_bundle/task_bundle.rego @@ -17,6 +17,23 @@ import rego.v1 import data.lib import data.lib.tekton +# ============================================================================= +# MANIFEST PRE-FETCHING +# Collect all bundle references and fetch manifests in a single batch call. +# ============================================================================= + +# Collect all unique bundle references from tasks in the pipeline definition +_all_bundle_refs := [tekton.task_ref(task).bundle | + some task in input.spec.tasks + tekton.task_ref(task).bundle != "" +] + +# Batch fetch all manifests at once using ec.oci.image_manifests +# This returns a map of bundle_ref -> manifest +_manifests := ec.oci.image_manifests(_all_bundle_refs) + +# ============================================================================= + # METADATA # title: Unpinned task bundle reference # description: >- @@ -89,7 +106,7 @@ deny contains result if { # failure_msg: Pipeline task '%s' uses an untrusted task bundle '%s' # deny contains result if { - some task in tekton.untrusted_task_refs(input.spec.tasks) + some task in tekton.untrusted_task_refs(input.spec.tasks, _manifests) bundle := tekton.bundle(task) bundle != "" result := lib.result_helper(rego.metadata.chain(), [task.name, bundle]) diff --git a/policy/release/lib/attestations.rego b/policy/release/lib/attestations.rego index 65702854a..181dcc88f 100644 --- a/policy/release/lib/attestations.rego +++ b/policy/release/lib/attestations.rego @@ -135,6 +135,14 @@ tasks_from_pipelinerun := [task | some task in tekton.tasks(att) ] +# Collect all unique bundle references from tasks in the pipelineRun attestation. +# Returns a set of bundle refs that can be passed to ec.oci.image_manifests. +pipelinerun_bundle_refs contains ref if { + some task in tasks_from_pipelinerun + ref := tekton.task_ref(task).bundle + ref != "" +} + # All results from the attested PipelineRun with the provided name. Results are # expected to contain a JSON value. The return object contains the following # keys: diff --git a/policy/release/slsa_build_scripted_build/slsa_build_scripted_build.rego b/policy/release/slsa_build_scripted_build/slsa_build_scripted_build.rego index ce9314510..3e39e7570 100644 --- a/policy/release/slsa_build_scripted_build/slsa_build_scripted_build.rego +++ b/policy/release/slsa_build_scripted_build/slsa_build_scripted_build.rego @@ -18,6 +18,9 @@ import data.lib import data.lib.image import data.lib.tekton +# Batch fetch all manifests for tasks in the pipelineRun attestation +_manifests := ec.oci.image_manifests(lib.pipelinerun_bundle_refs) + # METADATA # title: Build task contains steps # description: >- @@ -153,7 +156,7 @@ _trusted_build_task_error(tasks) := error if { count(tasks) == 0 error := "No Pipeline Tasks built the image" } else := error if { - untrusted_tasks := tekton.untrusted_task_refs(lib.tasks_from_pipelinerun) + untrusted_tasks := tekton.untrusted_task_refs(lib.tasks_from_pipelinerun, _manifests) untrusted_build_tasks = untrusted_tasks & tasks count(untrusted_build_tasks) > 0 diff --git a/policy/release/tasks/tasks.rego b/policy/release/tasks/tasks.rego index d5cb3e13a..6d90efb5e 100644 --- a/policy/release/tasks/tasks.rego +++ b/policy/release/tasks/tasks.rego @@ -31,6 +31,9 @@ import data.lib import data.lib.json as j import data.lib.tekton +# Batch fetch all manifests for tasks in the pipelineRun attestation +_manifests := ec.oci.image_manifests(lib.pipelinerun_bundle_refs) + # METADATA # title: Required tasks list for pipeline was provided # description: >- @@ -184,7 +187,7 @@ deny contains result if { flattened_required_tasks := flatten_list_to_sorted_array(required_task_names) some att in lib.pipelinerun_attestations - some untrusted_task in tekton.untrusted_task_refs(lib.tasks_from_pipelinerun) + some untrusted_task in tekton.untrusted_task_refs(lib.tasks_from_pipelinerun, _manifests) # Check if any untrusted task matches a required task some required_task_name in flattened_required_tasks diff --git a/policy/release/trusted_task/trusted_task.rego b/policy/release/trusted_task/trusted_task.rego index b6310b870..c78d39467 100644 --- a/policy/release/trusted_task/trusted_task.rego +++ b/policy/release/trusted_task/trusted_task.rego @@ -18,6 +18,9 @@ import data.lib import data.lib.image import data.lib.tekton +# Batch fetch all manifests for tasks in the pipelineRun attestation +_manifests := ec.oci.image_manifests(lib.pipelinerun_bundle_refs) + _supported_ta_uris_reg := {"oci:.*@sha256:[0-9a-f]{64}"} _digest_patterns := {`sha256:[0-9a-f]{64}`} @@ -123,7 +126,7 @@ warn contains result if { warn contains result if { not tekton.missing_trusted_task_rules_data some task in lib.tasks_from_pipelinerun - some rule in tekton.future_deny_rules_for_task(task) + some rule in tekton.future_deny_rules_for_task(task, _manifests) result := lib.result_helper_with_term( rego.metadata.chain(), [tekton.pipeline_task_name(task), rule.pattern, rule.effective_on], @@ -369,7 +372,7 @@ _task_info(task) := info if { _trusted_build_digests contains digest if { some attestation in lib.pipelinerun_attestations some build_task in tekton.build_tasks(attestation) - tekton.is_trusted_task(build_task) + tekton.is_trusted_task(build_task, _manifests) some result in tekton.task_results(build_task) some digest in _digests_from_values(lib.result_values(result)) } @@ -386,7 +389,7 @@ _trusted_build_digests contains digest if { _trusted_build_digests contains digest if { some attestation in lib.pipelinerun_attestations some task in tekton.pre_build_tasks(attestation) - tekton.is_trusted_task(task) + tekton.is_trusted_task(task, _manifests) runner_image_result_value := tekton.task_result(task, _pre_build_run_script_runner_image_result) some digest in _digests_from_values({runner_image_result_value}) } @@ -423,7 +426,7 @@ _trust_errors_rules contains error if { link == tekton.pipeline_task_name(task) ] - some untrusted_task in tekton.untrusted_task_refs_rules(chain) + some untrusted_task in tekton.untrusted_task_refs_rules(chain, _manifests) error := _format_trust_error_rules_ta(untrusted_task, dependency_chain) } @@ -431,7 +434,7 @@ _trust_errors_rules contains error if { # Collects trust errors using trusted_task_rules (without Trusted Artifacts) _trust_errors_rules contains error if { not _uses_trusted_artifacts - some untrusted_task in tekton.untrusted_task_refs_rules(lib.tasks_from_pipelinerun) + some untrusted_task in tekton.untrusted_task_refs_rules(lib.tasks_from_pipelinerun, _manifests) error := _format_trust_error_rules(untrusted_task) } @@ -455,7 +458,7 @@ _format_denial_reason(reason) := msg if { # Format error for rules system with Trusted Artifacts _format_trust_error_rules_ta(task, dependency_chain) := error if { - reason := tekton.denial_reason(task) + reason := tekton.denial_reason(task, _manifests) untrusted_pipeline_task_name := tekton.pipeline_task_name(task) untrusted_task_name := tekton.task_name(task) @@ -473,7 +476,7 @@ _format_trust_error_rules_ta(task, dependency_chain) := error if { # Format error for rules system without Trusted Artifacts _format_trust_error_rules(task) := error if { - reason := tekton.denial_reason(task) + reason := tekton.denial_reason(task, _manifests) untrusted_pipeline_task_name := tekton.pipeline_task_name(task) untrusted_task_name := tekton.task_name(task) untrusted_task_info := _task_info(task)