From 4c900a4d8acef1d0e0e2d20fa0f5ddd244ca9605 Mon Sep 17 00:00:00 2001 From: jstuart Date: Fri, 6 Mar 2026 11:38:21 -0600 Subject: [PATCH] add option for trusted registries --- policy/release/trusted_task/trusted_task.rego | 29 ++++- .../trusted_task/trusted_task_test.rego | 122 ++++++++++++++++++ 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/policy/release/trusted_task/trusted_task.rego b/policy/release/trusted_task/trusted_task.rego index b6310b870..b377a7906 100644 --- a/policy/release/trusted_task/trusted_task.rego +++ b/policy/release/trusted_task/trusted_task.rego @@ -162,13 +162,14 @@ deny contains result if { # description: >- # Confirm certain parameters provided to each builder Task have come from trusted Tasks. # Trust can be defined using pattern-based rules (trusted_task_rules) or an explicit allow -# list with expiry dates (trusted_tasks). +# list with expiry dates (trusted_tasks). Additionally, images from registries listed in the +# `trusted_build_image_registries` rule data key are implicitly trusted. # custom: # short_name: trusted_parameters # failure_msg: 'The %q parameter of the %q PipelineTask includes an untrusted digest: %s' # solution: >- # Update your build Pipeline to ensure all the parameters provided to your builder Tasks come -# from trusted Tasks. +# from trusted Tasks, or add the image registry to the trusted_build_image_registries rule data. # collections: # - redhat # effective_on: 2021-07-04T00:00:00Z @@ -391,8 +392,27 @@ _trusted_build_digests contains digest if { some digest in _digests_from_values({runner_image_result_value}) } +# Digests from images in trusted registries are considered trustworthy. +# Trusted registries are configured via the `trusted_build_image_registries` rule data key. +_trusted_build_digests contains digest if { + some attestation in lib.pipelinerun_attestations + some build_task in tekton.build_tasks(attestation) + some param_name, param_value in tekton.task_params(build_task) + not endswith(param_name, "_ARTIFACT") + some value in lib.param_values(param_value) + _from_trusted_registry(value) + some digest in _digests_from_values({value}) +} + _pre_build_run_script_runner_image_result := "SCRIPT_RUNNER_IMAGE_REFERENCE" +# Returns true if the given value is an image reference from a trusted registry. +_from_trusted_registry(value) if { + parsed := image.parse(value) + some registry in lib.rule_data("trusted_build_image_registries") + startswith(parsed.repo, registry) +} + # Extracts SHA256 digests from a set of values using regex patterns _digests_from_values(values) := {digest | some value in values @@ -451,6 +471,11 @@ _format_denial_reason(reason) := msg if { pattern_lines := [sprintf(" - %s", [pattern]) | some pattern in reason.pattern] msg := sprintf("%s\n%s", [reason.type, concat("\n", pattern_lines)]) +} else := msg if { + reason.type == "signature_verification_failed" + count(reason.messages) > 0 + message_lines := [sprintf(" - %s", [m]) | some m in reason.messages] + msg := sprintf("%s\n%s", [reason.type, concat("\n", message_lines)]) } else := reason.type # Format error for rules system with Trusted Artifacts diff --git a/policy/release/trusted_task/trusted_task_test.rego b/policy/release/trusted_task/trusted_task_test.rego index d4cb51db0..732721f49 100644 --- a/policy/release/trusted_task/trusted_task_test.rego +++ b/policy/release/trusted_task/trusted_task_test.rego @@ -553,6 +553,99 @@ test_trusted_build_digests_from_snapshot_components if { lib.assert_equal(trusted_task._trusted_build_digests, expected) with input.snapshot.components as components } +test_trusted_build_digests_from_trusted_registry if { + # A digest from a parameter referencing a trusted registry should appear in _trusted_build_digests + attestation := _mock_att_with_task({ + "ref": {"name": "some-task", "bundle": "registry.local/trusty:1.0@sha256:digest"}, + "results": [ + {"name": "SOME_IMAGE_URL", "value": "registry.io/whatever/image", "type": "string"}, + # regal ignore:line-length + {"name": "SOME_IMAGE_DIGEST", "value": "sha256:2222222222222222222222222222222222222222222222222222222222222222", "type": "string"}, + ], + # regal ignore:line-length + "invocation": {"parameters": {"image": "trusted.registry.io/repository/image@sha256:5555555555555555555555555555555555555555555555555555555555555555"}}, + }) + expected := { + # From build task results (existing behavior) + "sha256:2222222222222222222222222222222222222222222222222222222222222222", + # From trusted registry parameter (new behavior) + "sha256:5555555555555555555555555555555555555555555555555555555555555555", + } + lib.assert_equal(trusted_task._trusted_build_digests, expected) with input.attestations as [attestation] + with data.trusted_tasks as trusted_tasks_data + with data.rule_data.trusted_build_image_registries as ["trusted.registry.io/"] +} + +test_trusted_build_digests_from_untrusted_registry if { + # A digest from a parameter referencing an untrusted registry should NOT appear in _trusted_build_digests + attestation := _mock_att_with_task({ + "ref": {"name": "some-task", "bundle": "registry.local/trusty:1.0@sha256:digest"}, + "results": [ + {"name": "SOME_IMAGE_URL", "value": "registry.io/whatever/image", "type": "string"}, + # regal ignore:line-length + {"name": "SOME_IMAGE_DIGEST", "value": "sha256:2222222222222222222222222222222222222222222222222222222222222222", "type": "string"}, + ], + # regal ignore:line-length + "invocation": {"parameters": {"image": "untrusted.registry.io/repository/image@sha256:5555555555555555555555555555555555555555555555555555555555555555"}}, + }) + + # Only the build task result digest should be trusted, not the parameter digest + expected := {"sha256:2222222222222222222222222222222222222222222222222222222222222222"} + lib.assert_equal(trusted_task._trusted_build_digests, expected) with input.attestations as [attestation] + with data.trusted_tasks as trusted_tasks_data + with data.rule_data.trusted_build_image_registries as ["trusted.registry.io/"] +} + +test_trusted_parameters_with_trusted_registry if { + # When a parameter references a trusted registry, the deny rule should not fire + evil_attestation := json.patch(attestation_ta, [{ + "op": "add", + "path": "/statement/predicate/buildConfig/tasks/3/invocation/parameters/image", + # regal ignore:line-length + "value": "trusted.registry.io/repository/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }]) + + # With the registry trusted, no violation should be produced + lib.assert_empty(trusted_task.deny) with data.trusted_tasks as trusted_tasks_data + with input.attestations as [evil_attestation] + with data.rule_data.trusted_build_image_registries as ["trusted.registry.io/"] +} + +test_trusted_parameters_with_untrusted_registry if { + # When a parameter references an untrusted registry, the deny rule should fire + evil_attestation := json.patch(attestation_ta, [{ + "op": "add", + "path": "/statement/predicate/buildConfig/tasks/3/invocation/parameters/image", + # regal ignore:line-length + "value": "untrusted.registry.io/repository/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }]) + + lib.assert_equal_results(trusted_task.deny, {{ + "code": "trusted_task.trusted_parameters", + # regal ignore:line-length + "msg": `The "image" parameter of the "task_image_index" PipelineTask includes an untrusted digest: sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`, + }}) with data.trusted_tasks as trusted_tasks_data + with input.attestations as [evil_attestation] + with data.rule_data.trusted_build_image_registries as ["trusted.registry.io/"] +} + +test_from_trusted_registry if { + # regal ignore:line-length + trusted_task._from_trusted_registry("trusted.io/repo/image@sha256:abc123") with data.rule_data.trusted_build_image_registries as ["trusted.io/"] + + # regal ignore:line-length + trusted_task._from_trusted_registry("trusted.io/repo/image:tag@sha256:abc123") with data.rule_data.trusted_build_image_registries as ["trusted.io/repo"] + + # regal ignore:line-length + not trusted_task._from_trusted_registry("untrusted.io/repo/image@sha256:abc123") with data.rule_data.trusted_build_image_registries as ["trusted.io/"] + + # regal ignore:line-length + not trusted_task._from_trusted_registry("not-an-image-ref") with data.rule_data.trusted_build_image_registries as ["trusted.io/"] + + # regal ignore:line-length + not trusted_task._from_trusted_registry("trusted.io/repo/image@sha256:abc123") with data.rule_data.trusted_build_image_registries as [] +} + ######################################### # Pipeline Tasks using bundles resolver # ######################################### @@ -1310,6 +1403,35 @@ test_mixed_trusted_and_untrusted_tasks if { with data.rule_data.trusted_task_rules as trusted_task_rules_data } +test_signature_verification_failed_error_rules if { + att := {"statement": { + "predicateType": "https://slsa.dev/provenance/v0.2", + "predicate": { + "buildType": lib.tekton_pipeline_run, + "buildConfig": {"tasks": [trusted_bundle_pipeline_task]}, + }, + }} + + rules := {"allow": [{ + "name": "signed catalog", + "pattern": "oci://registry.local/trusty*", + "signature_verification": { + "certificate_identity_regexp": "https://tekton.dev/chains/.*", + "certificate_oidc_issuer": "https://accounts.google.com", + }, + }]} + + results := trusted_task.deny with input.attestations as [att] + with data.trusted_task_rules as rules + with ec.sigstore.verify_image as _mock_verify_image_failure + + count(results) > 0 + some result in results + contains(result.msg, "signature_verification_failed") +} + +_mock_verify_image_failure(_, _) := {"success": false, "errors": ["signature verification failed"]} + ##################################################### # Helper Functions for trusted_task_rules tests #####################################################