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", }, ]}