From f5c8486454b287bb52a4f51d3dad988b9fa98f21 Mon Sep 17 00:00:00 2001 From: Zoran Regvart Date: Fri, 10 Nov 2023 10:49:27 +0100 Subject: [PATCH] tags as versions for acceptable task rules This contains two major changes. One is that the logic in `lib.bundles` is written to in the form prose (or close enough) to allow for easier readings of the logic. Instead of the notion of a task collection and equality of task bundle references, the code now considers business logic terms, like "is acceptable", "is out of date", "is expired" and "newer version exists". And the second is that tags provided in acceptable task bundle data are now considered in rules. If tags are provided in acceptable task bundle they further refine the set of records from acceptable task bundle data that are examined, i.e. different versions do not interfere with each other. For task bundle references tags are computed from the acceptable task bundle data records by matching against the digest. reference: EC-223 --- policy/lib/bundles.rego | 219 ++++++++++++---- policy/lib/bundles_test.rego | 240 +++++++++++++++--- policy/pipeline/task_bundle_test.rego | 55 ++-- .../release/attestation_task_bundle_test.rego | 137 ++++++++-- 4 files changed, 522 insertions(+), 129 deletions(-) diff --git a/policy/lib/bundles.rego b/policy/lib/bundles.rego index 374641d0..b1a3f559 100644 --- a/policy/lib/bundles.rego +++ b/policy/lib/bundles.rego @@ -7,10 +7,13 @@ import data.lib.image import data.lib.refs import data.lib.time as time_lib +# Return the bundle reference as is +bundle(task) := refs.task_ref(task).bundle + # Returns a subset of tasks that do not use a bundle reference. disallowed_task_reference(tasks) := {task | some task in tasks - not refs.task_ref(task).bundle + not bundle(task) } # Returns a subset of tasks that use an empty bundle reference. @@ -26,73 +29,197 @@ unpinned_task_bundle(tasks) := {task | ref.digest == "" } +# Returns if the required task-bundles data is missing +default missing_task_bundles_data := false + +missing_task_bundles_data if { + count(data["task-bundles"]) == 0 +} + # Returns a subset of tasks that use an acceptable bundle reference, but # an updated bundle reference exists. out_of_date_task_bundle(tasks) := {task | some task in tasks - ref := image.parse(bundle(task)) - collection := _collection(ref) - some match_index, out_of_date in collection - is_equal(out_of_date, ref) - match_index > 0 + ref := image.parse(_bundle_ref(task, data["task-bundles"])) + + _newer_version_exists(ref) + not _is_unacceptable(ref) } # Returns a subset of tasks that do not use an acceptable bundle reference. unacceptable_task_bundle(tasks) := {task | some task in tasks - ref := image.parse(bundle(task)) - collection := _collection(ref) - matches := [record | - some record in collection - is_equal(record, ref) - ] + ref := image.parse(_bundle_ref(task, data["task-bundles"])) - count(matches) == 0 + _is_unacceptable(ref) } -# Returns if the required task-bundles data is missing -default missing_task_bundles_data := false +_is_unacceptable(ref) if { + not _record_exists(ref) +} -missing_task_bundles_data if { - count(data["task-bundles"]) == 0 +_is_unacceptable(ref) if { + _newer_in_effect_version_exists(ref) } -# Returns true if the provided bundle reference is acceptable -is_acceptable(bundle_ref) if { - ref := image.parse(bundle_ref) - collection := _collection(ref) - matches := [r | - some r in collection - is_equal(r, ref) - ] +# Returns true if the provided bundle reference is recorded within the +# acceptable bundles data +_record_exists(ref) if { + # all records in acceptable task bundles for the given repository + records := data["task-bundles"][ref.repo] + + some record in records - count(matches) > 0 + # an acceptable task bundle reference is one that is recorded in the + # acceptable task bundles, this is done by matching it's digest; note no + # care is given to the expiry or freshness + record.digest == ref.digest } -# Returns whether or not the ref matches the digest of the record. -is_equal(record, ref) := match if { - ref.digest != "" - match := record.digest == ref.digest +# Evaluates to true if the tasks bundle reference is found in the acceptable +# task bundles data, but also in the data there is a newer version of the task +# and it is effective, i.e. has a effective_on that is newer than the provided +# reference's effective_on and older or equal to the current effective time; two +# references are considered belonging to the same version if they have the same +# tag. +_newer_in_effect_version_exists(ref) if { + # all records in acceptable task bundles for the given repository + records := data["task-bundles"][ref.repo] + + some record in records + + # consider all records, if a match is found via exact digest and there + # exists a newer record for the same tag but it is newer, i.e. has greater + # effective_on value + record.digest == ref.digest + + some other in records + + # other record must be effective to be considered + time.parse_rfc3339_ns(other.effective_on) <= time_lib.effective_current_time_ns() + + record.tag == other.tag + + time.parse_rfc3339_ns(other.effective_on) > time.parse_rfc3339_ns(record.effective_on) } -# Returns whether or not the ref matches the tag of the record as a fallback -# in case the digest is blank for the ref. This is a weaker comparison as, -# unlike digests, tags are not immutable entities. It is expected that a -# missing digest results in a warning whenever possible. -is_equal(record, ref) := match if { - ref.digest == "" - match := record.tag == ref.tag +# Evaluates to true if the tasks bundle reference is found in the acceptable +# task bundles data, but also there are no records in acceptable task bundles +# data with the same tag and at least one record is newer and it is effective, +# i.e. has a effective_on that is newer than the provided reference's +# effective_on and older or equal to the current effective time. In this case we +# cannot rely on the tags to signal versions so we take all records for a +# specific reference to belong to the same version. +_newer_in_effect_version_exists(ref) if { + # all records in acceptable task bundles for the given repository + records := data["task-bundles"][ref.repo] + + some record in records + + # consider all records, if a match is found via exact digest and there + # exists a newer record for the same tag but it is newer, i.e. has greater + # effective_on value + record.digest == ref.digest + + # No other record in acceptable bundles matches the tag from the record + # matched by the digest to the reference + count([other | + some other in records + record.digest != other.digest # not the same record + record.tag == other.tag # we found at least one other tag equal to the one we want to compare with + ]) == 0 + + # There are newer records + count([newer | + some newer in records + time.parse_rfc3339_ns(newer.effective_on) <= time_lib.effective_current_time_ns() + time.parse_rfc3339_ns(newer.effective_on) > time.parse_rfc3339_ns(record.effective_on) + ]) > 0 } -bundle(task) := refs.task_ref(task).bundle +# Evaluates to true if the tasks bundle reference is found in the acceptable +# task bundles data, but also there are no records in acceptable task bundles +# data with the same tag and at least one record is newer, regardless of it's +# effective on date, i.e. has a effective_on that is newer than the provided +# reference's effective_on. Two references are considered belonging to the same +# version if they have the same tag. +_newer_version_exists(ref) if { + # all records in acceptable task bundles for the given repository + records := data["task-bundles"][ref.repo] + + some record in records -# _collection returns an array representing the full list of records to -# be taken into consideration when evaluating policy rules for bundle -# references. Any irrelevant records are filtered out from the array. -# (The else condition is for when data["task-bundles"][ref.repo] doesn't exist.) -_collection(ref) := items if { - full_collection := data["task-bundles"][ref.repo] - items := time_lib.acceptable_items(full_collection) -} else := [] + # consider all records, if a match is found via exact digest and there + # exists a newer record for the same tag but it is newer, i.e. has greater + # effective_on value + record.digest == ref.digest + + some other in records + + record.tag == other.tag + + time.parse_rfc3339_ns(other.effective_on) > time.parse_rfc3339_ns(record.effective_on) +} + +# Evaluates to true if the tasks bundle reference is found in the acceptable +# task bundles data, but also there are no records in acceptable task bundles +# data with the same tag and at least one record is newer, regardless of it's +# effective on date, i.e. has a effective_on that is newer than the provided +# reference's effective_on. In this case we cannot rely on the tags to signal +# versions so we take all records for a specific reference to belong to the same +# version. +_newer_version_exists(ref) if { + # all records in acceptable task bundles for the given repository + records := data["task-bundles"][ref.repo] + + some record in records + + # consider all records, if a match is found via exact digest and there + # exists a newer record for the same tag but it is newer, i.e. has greater + # effective_on value + record.digest == ref.digest + + # No other record in acceptable bundles matches the tag from the record + # matched by the digest to the reference + count([other | + some other in records + record.digest != other.digest # not the same record + record.tag == other.tag # we found at least one other tag equal to the one we want to compare with + ]) == 0 + + # There are newer records + count([newer | + some newer in records + time.parse_rfc3339_ns(newer.effective_on) > time.parse_rfc3339_ns(record.effective_on) + ]) > 0 +} + +# Determine the image reference of the task bundle, if the provided task bundle +# image reference doesn't have the tag within it try to lookup the tag from the +# acceptable task bundles data +_bundle_ref(task, acceptable) := ref if { + ref := bundle(task) + img := image.parse(ref) + img.tag != "" +} else := ref if { + ref_no_tag := bundle(task) + img := image.parse(ref_no_tag) + img.tag == "" + + # try to find the tag for the reference based on it's digest + records := acceptable[img.repo] + + some record in records + record.digest == img.digest + record.tag != "" + + ref := image.str({ + "digest": img.digest, + "repo": img.repo, + "tag": record.tag, + }) +} else := ref_no_tag if { + ref_no_tag := bundle(task) +} diff --git a/policy/lib/bundles_test.rego b/policy/lib/bundles_test.rego index 5f95346e..219d6463 100644 --- a/policy/lib/bundles_test.rego +++ b/policy/lib/bundles_test.rego @@ -5,6 +5,7 @@ import future.keywords.in import data.lib import data.lib.bundles +import data.lib.image # used as reference bundle data in tests bundle_data := {"registry.img/acceptable": [{ @@ -22,7 +23,7 @@ test_disallowed_task_reference if { {"name": "my-task-2", "ref": {}}, ] - expected := {task | some task in tasks} + expected := lib.to_set(tasks) lib.assert_equal(bundles.disallowed_task_reference(tasks), expected) } @@ -32,7 +33,7 @@ test_empty_task_bundle_reference if { {"name": "my-task-2", "ref": {"bundle": ""}}, ] - expected := {task | some task in tasks} + expected := lib.to_set(tasks) lib.assert_equal(bundles.empty_task_bundle_reference(tasks), expected) } @@ -48,8 +49,8 @@ test_unpinned_task_bundle if { }, ] - expected := {task | some task in tasks} - lib.assert_equal(bundles.unpinned_task_bundle(tasks), expected) + expected := lib.to_set(tasks) + lib.assert_equal(bundles.unpinned_task_bundle(tasks), expected) with data["task-bundles"] as [] } # All good when the most recent bundle is used. @@ -69,79 +70,236 @@ test_acceptable_bundle if { test_out_of_date_task_bundle if { tasks := [ {"name": "my-task-1", "taskRef": {"bundle": "reg.com/repo@sha256:bcd"}}, - {"name": "my-task-2", "taskRef": {"bundle": "reg.com/repo@sha256:cde"}}, {"name": "my-task-3", "ref": {"bundle": "reg.com/repo@sha256:bcd"}}, - {"name": "my-task-4", "ref": {"bundle": "reg.com/repo@sha256:cde"}}, ] - expected := {task | some task in tasks} + lib.assert_empty(bundles.out_of_date_task_bundle(tasks)) with data["task-bundles"] as task_bundles + + expected := lib.to_set(tasks) lib.assert_equal(bundles.out_of_date_task_bundle(tasks), expected) with data["task-bundles"] as task_bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2022-03-12T00:00:00Z") } test_unacceptable_task_bundles if { tasks := [ - {"name": "my-task-1", "taskRef": {"bundle": "reg.com/repo@sha256:def"}}, - {"name": "my-task-2", "ref": {"bundle": "reg.com/repo@sha256:def"}}, + {"name": "my-task-1", "taskRef": {"bundle": "reg.com/repo@sha256:blah"}}, + {"name": "my-task-2", "ref": {"bundle": "reg.com/repo@sha256:blah"}}, + {"name": "my-task-3", "ref": {"bundle": "wat.com/repo@sha256:blah"}}, ] - expected := {task | some task in tasks} + expected := lib.to_set(tasks) lib.assert_equal(bundles.unacceptable_task_bundle(tasks), expected) with data["task-bundles"] as task_bundles } -test_is_equal if { - record := {"digest": "sha256:abc", "tag": "spam"} - - # Exact match - lib.assert_equal(bundles.is_equal(record, {"digest": "sha256:abc", "tag": "spam"}), true) - - # Tag is ignored if digest matches - lib.assert_equal(bundles.is_equal(record, {"digest": "sha256:abc", "tag": "not-spam"}), true) - - # Tag is not required - lib.assert_equal(bundles.is_equal(record, {"digest": "sha256:abc", "tag": ""}), true) - - # When digest is missing on ref, compare tag - lib.assert_equal(bundles.is_equal(record, {"digest": "", "tag": "spam"}), true) - - # If digest does not match, tag is still ignored - lib.assert_equal(bundles.is_equal(record, {"digest": "sha256:bcd", "tag": "spam"}), false) - - # No match is honored when digest is missing - lib.assert_equal(bundles.is_equal(record, {"digest": "", "tag": "not-spam"}), false) -} - task_bundles := {"reg.com/repo": [ { "digest": "sha256:abc", # Allow - "tag": "903d49a833d22f359bce3d67b15b006e1197bae5", - "effective_on": "2262-04-11T00:00:00Z", + "tag": "v1", + "effective_on": "2022-04-11T00:00:00Z", }, { "digest": "sha256:bcd", # Warn - "tag": "b7d8f6ae908641f5f2309ee6a9d6b2b83a56e1af", - "effective_on": "2262-03-11T00:00:00Z", + "tag": "v1", + "effective_on": "2022-03-11T00:00:00Z", }, { "digest": "sha256:cde", # Warn - "tag": "120dda49a6cc3b89516b491e19fe1f3a07f1427f", + "tag": "v1", "effective_on": "2022-02-01T00:00:00Z", }, { "digest": "sha256:def", # Warn - "tag": "903d49a833d22f359bce3d67b15b006e1197bae5", + "tag": "v1", "effective_on": "2021-01-01T00:00:00Z", }, ]} -test_acceptable_bundle_is_acceptable if { - bundles.is_acceptable(acceptable_bundle_ref) with data["task-bundles"] as bundle_data +test_acceptable_bundle_record_exists if { + bundles._record_exists(image.parse(acceptable_bundle_ref)) with data["task-bundles"] as bundle_data } test_unacceptable_bundle_is_unacceptable if { - not bundles.is_acceptable("registry.img/unacceptable@sha256:digest") with data["task-bundles"] as bundle_data + ref := "registry.img/unacceptable@sha256:digest" + not bundles._record_exists(image.parse(ref)) with data["task-bundles"] as bundle_data } test_missing_required_data if { lib.assert_equal(bundles.missing_task_bundles_data, false) with data["task-bundles"] as task_bundles lib.assert_equal(bundles.missing_task_bundles_data, true) with data["task-bundles"] as [] } + +test_newer_in_effect_version_exists_not_using_tags_newest if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [{ + "digest": "sha256:digest", + "tag": "", + "effective_on": "2262-04-11T00:00:00Z", + }]} + not bundles._newer_in_effect_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_in_effect_version_exists_not_using_tags_older if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:newer", + "tag": "", + "effective_on": "2022-04-11T00:00:00Z", + }, + { + "digest": "sha256:digest", + "tag": "", + "effective_on": "1962-04-11T00:00:00Z", + }, + ]} + bundles._newer_in_effect_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_in_effect_version_exists_tags_differ_newest if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [{ + "digest": "sha256:digest", + "tag": "different", + "effective_on": "2262-04-11T00:00:00Z", + }]} + not bundles._newer_in_effect_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_in_effect_version_exists_tags_differ_older if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:newer", + "tag": "newer", + "effective_on": "2022-04-11T00:00:00Z", + }, + { + "digest": "sha256:digest", + "tag": "different", + "effective_on": "1962-04-11T00:00:00Z", + }, + ]} + bundles._newer_in_effect_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_in_effect_version_exists_tags_as_versions_newest if { + ref := image.parse("registry.io/repository/image:v1@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:digest", + "tag": "v1", + "effective_on": "2262-04-11T00:00:00Z", + }, + { + "digest": "sha256:different", + "tag": "v1", + "effective_on": "2162-04-11T00:00:00Z", + }, + ]} + not bundles._newer_in_effect_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_in_effect_version_exists_tags_as_versions_older if { + ref := image.parse("registry.io/repository/image:v1@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:newer", + "tag": "v1", + "effective_on": "2022-04-11T00:00:00Z", + }, + { + "digest": "sha256:digest", + "tag": "v1", + "effective_on": "1962-04-11T00:00:00Z", + }, + ]} + bundles._newer_in_effect_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_version_exists_not_using_tags_newest if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [{ + "digest": "sha256:digest", + "tag": "", + "effective_on": "2262-04-11T00:00:00Z", + }]} + not bundles._newer_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_version_exists_not_using_tags_older if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:newer", + "tag": "", + "effective_on": "2262-04-11T00:00:00Z", + }, + { + "digest": "sha256:digest", + "tag": "", + "effective_on": "1962-04-11T00:00:00Z", + }, + ]} + bundles._newer_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_version_exists_tags_differ_newest if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [{ + "digest": "sha256:digest", + "tag": "different", + "effective_on": "2262-04-11T00:00:00Z", + }]} + not bundles._newer_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_version_exists_tags_differ_older if { + ref := image.parse("registry.io/repository/image:tag@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:newer", + "tag": "newer", + "effective_on": "2262-04-11T00:00:00Z", + }, + { + "digest": "sha256:digest", + "tag": "different", + "effective_on": "1962-04-11T00:00:00Z", + }, + ]} + bundles._newer_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_version_exists_tags_as_versions_newest if { + ref := image.parse("registry.io/repository/image:v1@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:digest", + "tag": "v1", + "effective_on": "2262-04-11T00:00:00Z", + }, + { + "digest": "sha256:different", + "tag": "v1", + "effective_on": "2162-04-11T00:00:00Z", + }, + ]} + not bundles._newer_version_exists(ref) with data["task-bundles"] as acceptable +} + +test_newer_version_exists_tags_as_versions_older if { + ref := image.parse("registry.io/repository/image:v1@sha256:digest") + acceptable := {"registry.io/repository/image": [ + { + "digest": "sha256:newer", + "tag": "v1", + "effective_on": "2262-04-11T00:00:00Z", + }, + { + "digest": "sha256:digest", + "tag": "v1", + "effective_on": "1962-04-11T00:00:00Z", + }, + ]} + bundles._newer_version_exists(ref) with data["task-bundles"] as acceptable +} diff --git a/policy/pipeline/task_bundle_test.rego b/policy/pipeline/task_bundle_test.rego index 33d7899e..4dc163b1 100644 --- a/policy/pipeline/task_bundle_test.rego +++ b/policy/pipeline/task_bundle_test.rego @@ -37,7 +37,7 @@ test_bundle_unpinned if { lib.assert_equal_results(task_bundle.warn, {{ "code": "task_bundle.unpinned_task_bundle", "msg": "Pipeline task 'my-task' uses an unpinned task bundle reference 'reg.com/repo:latest'", - }}) with input.spec.tasks as tasks + }}) with input.spec.tasks as tasks with data["task-bundles"] as [] } test_bundle_reference_valid if { @@ -61,6 +61,17 @@ test_acceptable_bundle_up_to_date if { with data["task-bundles"] as task_bundles } +# All good when the most recent bundle is used for a version that is still maintained +test_acceptable_bundle_up_to_date_maintained_version if { + tasks := [{"name": "my-task", "taskRef": {"bundle": "reg.com/repo@sha256:ghi"}}] + + lib.assert_empty(task_bundle.warn) with input.spec.tasks as tasks + with data["task-bundles"] as task_bundles + + lib.assert_empty(task_bundle.deny) with input.spec.tasks as tasks + with data["task-bundles"] as task_bundles +} + # Warn about out of date bundles that are still acceptable. test_acceptable_bundle_out_of_date_past if { tasks := [ @@ -68,20 +79,16 @@ test_acceptable_bundle_out_of_date_past if { {"name": "my-task-2", "taskRef": {"bundle": "reg.com/repo@sha256:cde"}}, ] - lib.assert_equal_results(task_bundle.warn, { - { - "code": "task_bundle.out_of_date_task_bundle", - "msg": "Pipeline task 'my-task-1' uses an out of date task bundle 'reg.com/repo@sha256:bcd'", - }, - { - "code": "task_bundle.out_of_date_task_bundle", - "msg": "Pipeline task 'my-task-2' uses an out of date task bundle 'reg.com/repo@sha256:cde'", - }, - }) with input.spec.tasks as tasks + lib.assert_equal_results(task_bundle.warn, {{ + "code": "task_bundle.out_of_date_task_bundle", + "msg": "Pipeline task 'my-task-1' uses an out of date task bundle 'reg.com/repo@sha256:bcd'", + }}) with input.spec.tasks as tasks with data["task-bundles"] as task_bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2022-03-12T00:00:00Z") lib.assert_empty(task_bundle.deny) with input.spec.tasks as tasks with data["task-bundles"] as task_bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2022-03-12T00:00:00Z") } # Deny bundles that are no longer active. @@ -108,27 +115,33 @@ test_missing_required_data if { task_bundles := {"reg.com/repo": [ { - # Latest bundle, allowed + # Latest v2 "digest": "sha256:abc", - "tag": "", - "effective_on": "2262-04-11T00:00:00Z", + "tag": "v2", + "effective_on": "2022-04-11T00:00:00Z", + }, + { + # Latest v3 + "digest": "sha256:ghi", + "tag": "v3", + "effective_on": "2022-04-11T00:00:00Z", }, { - # Recent bundle effective in the future, allowed but warn to upgrade + # Older v2 "digest": "sha256:bcd", - "tag": "", - "effective_on": "2262-03-11T00:00:00Z", + "tag": "v2", + "effective_on": "2022-03-11T00:00:00Z", }, { - # Recent bundle effective in the past, allowed but warn to upgrade + # Latest v1 "digest": "sha256:cde", - "tag": "", + "tag": "v1", "effective_on": "2022-02-01T00:00:00Z", }, { - # Old bundle, denied + # Older v1 "digest": "sha256:def", - "tag": "", + "tag": "v1", "effective_on": "2021-01-01T00:00:00Z", }, ]} diff --git a/policy/release/attestation_task_bundle_test.rego b/policy/release/attestation_task_bundle_test.rego index a6c8951c..7afb13c7 100644 --- a/policy/release/attestation_task_bundle_test.rego +++ b/policy/release/attestation_task_bundle_test.rego @@ -71,7 +71,7 @@ test_bundle_unpinned if { lib.assert_equal_results(attestation_task_bundle.warn, {{ "code": "attestation_task_bundle.task_ref_bundles_pinned", "msg": expected_msg, - }}) with input.attestations as attestations + }}) with input.attestations as attestations with data["task-bundles"] as [] } test_bundle_reference_valid if { @@ -166,20 +166,16 @@ test_acceptable_bundle_out_of_date_past if { lib_test.mock_slsav1_attestation_bundles(images), ] - lib.assert_equal_results(attestation_task_bundle.warn, { - { - "code": "attestation_task_bundle.task_ref_bundles_current", - "msg": "Pipeline task 'my-task' uses an out of date task bundle 'reg.com/repo@sha256:bcd'", - }, - { - "code": "attestation_task_bundle.task_ref_bundles_current", - "msg": "Pipeline task 'my-task' uses an out of date task bundle 'reg.com/repo@sha256:cde'", - }, - }) with input.attestations as attestations + lib.assert_equal_results(attestation_task_bundle.warn, {{ + "code": "attestation_task_bundle.task_ref_bundles_current", + "msg": "Pipeline task 'my-task' uses an out of date task bundle 'reg.com/repo@sha256:bcd'", + }}) with input.attestations as attestations with data["task-bundles"] as task_bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2022-03-12T00:00:00Z") lib.assert_empty(attestation_task_bundle.deny) with input.attestations as attestations with data["task-bundles"] as task_bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2022-03-12T00:00:00Z") } # Deny bundles that are no longer active. @@ -207,29 +203,128 @@ test_acceptable_bundles_provided if { lib.assert_equal_results(expected, attestation_task_bundle.deny) with data["task-bundles"] as [] } +test_warn_cases if { + bundles := {"q.io/r//task-buildah": [ + { + "digest": "sha256:c37e54", + "effective_on": "2023-11-06T00:00:00Z", + "tag": "0.1", + }, + { + "digest": "sha256:97f216", + "effective_on": "2023-10-25T00:00:00Z", + "tag": "0.1", + }, + { + "digest": "sha256:487b82", + "effective_on": "2023-10-21T00:00:00Z", + "tag": "0.1", + }, + ]} + + attestation_c37e54 := mock_data({"ref": { + "name": "buildah", + "bundle": "q.io/r//task-buildah@sha256:c37e54", + }}) + + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_c37e54] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-07T00:00:00Z") + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_c37e54] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-06T00:00:00Z") + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_c37e54] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-05T00:00:00Z") + + attestation_97f216 := mock_data({"ref": { + "name": "buildah", + "bundle": "q.io/r//task-buildah@sha256:97f216", + }}) + + expected_97f216 := {{ + "code": "attestation_task_bundle.task_ref_bundles_current", + "msg": "Pipeline task 'buildah' uses an out of date task bundle 'q.io/r//task-buildah@sha256:97f216'", + }} + + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_97f216] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-07T00:00:00Z") + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_97f216] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-06T00:00:00Z") + lib.assert_equal_results( + expected_97f216, + attestation_task_bundle.warn, + ) with input.attestations as [attestation_97f216] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-05T00:00:00Z") + lib.assert_equal_results( + expected_97f216, + attestation_task_bundle.warn, + ) with input.attestations as [attestation_97f216] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-10-25T00:00:00Z") + + attestation_487b82 := mock_data({"ref": { + "name": "buildah", + "bundle": "q.io/r//task-buildah@sha256:487b82", + }}) + + expected_487b82 := {{ + "code": "attestation_task_bundle.task_ref_bundles_current", + "msg": "Pipeline task 'buildah' uses an out of date task bundle 'q.io/r//task-buildah@sha256:487b82'", + }} + + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_487b82] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-07T00:00:00Z") + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_487b82] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-06T00:00:00Z") + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_487b82] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-11-05T00:00:00Z") + lib.assert_empty(attestation_task_bundle.warn) with input.attestations as [attestation_487b82] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-10-25T00:00:00Z") + lib.assert_equal_results( + expected_487b82, + attestation_task_bundle.warn, + ) with input.attestations as [attestation_487b82] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-10-21T00:00:00Z") + lib.assert_equal_results( + expected_487b82, + attestation_task_bundle.warn, + ) with input.attestations as [attestation_487b82] + with data["task-bundles"] as bundles + with data.config.policy.when_ns as time.parse_rfc3339_ns("2023-10-22T00:00:00Z") +} + task_bundles := {"reg.com/repo": [ { - # Latest bundle, allowed + # Latest v2 "digest": "sha256:abc", - "tag": "", - "effective_on": "2262-04-11T00:00:00Z", + "tag": "v2", + "effective_on": "2022-04-11T00:00:00Z", }, { - # Recent bundle effective in the future, allowed but attestation_task_bundle.warn to upgrade + # Older v2 "digest": "sha256:bcd", - "tag": "", - "effective_on": "2262-03-11T00:00:00Z", + "tag": "v2", + "effective_on": "2022-03-11T00:00:00Z", }, { - # Recent bundle effective in the past, allowed but attestation_task_bundle.warn to upgrade + # Latest v1 "digest": "sha256:cde", - "tag": "", + "tag": "v1", "effective_on": "2022-02-01T00:00:00Z", }, { - # Old bundle, denied + # Older v1 "digest": "sha256:def", - "tag": "", + "tag": "v1", "effective_on": "2021-01-01T00:00:00Z", }, ]}