Skip to content

Commit 9641116

Browse files
committed
Add ec.oci.image_manifest rego function
This commit adds a new custom rego function, `ec.oci.image_manifest`. This function retrieves the Image Manifest from an OCI registry. (It does not download the image, just its manifest.) The main use case this is trying to achieve is validating image references that may occur in the SLSA Provenance attestation of an image being validated. For example, the SLSA Provenance may contain a link to the corresponding source container image. This function allows policy rules to be created to verify that such references actually exists. Ref: EC-235 Signed-off-by: Luiz Carvalho <[email protected]>
1 parent 379e101 commit 9641116

File tree

7 files changed

+1585
-4
lines changed

7 files changed

+1585
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package manifest
2+
3+
import rego.v1
4+
5+
# METADATA
6+
# custom:
7+
# short_name: match
8+
deny contains result if {
9+
manifest := ec.oci.image_manifest(input.image.ref)
10+
not manifest_matches(manifest)
11+
12+
result := {
13+
"code": "manifest.match",
14+
"msg": json.marshal(manifest),
15+
}
16+
}
17+
18+
manifest_matches(manifest) if {
19+
manifest.annotations["org.opencontainers.image.base.name"] != ""
20+
manifest.mediaType == "application/vnd.docker.distribution.manifest.v2+json"
21+
manifest.schemaVersion == 2
22+
non_empty_descriptor(manifest.config, "application/vnd.docker.container.image.v1+json")
23+
count(manifest.layers) == 2
24+
non_empty_descriptor(manifest.layers[0], "application/vnd.docker.image.rootfs.diff.tar.gzip")
25+
non_empty_descriptor(manifest.layers[1], "application/vnd.docker.image.rootfs.diff.tar.gzip")
26+
}
27+
28+
non_empty_descriptor(descriptor, media_type) if {
29+
descriptor.annotations == {}
30+
descriptor.artifactType == ""
31+
descriptor.data == ""
32+
startswith(descriptor.digest, "sha256:")
33+
count(descriptor.digest) == count("sha256:") + 64
34+
descriptor.mediaType == media_type
35+
descriptor.size > 0
36+
descriptor.urls == []
37+
}

features/__snapshots__/validate_image.snap

+77
Original file line numberDiff line numberDiff line change
@@ -3344,3 +3344,80 @@ time="${TIMESTAMP}" level=error msg="Parsing PURL \"this-is-not-a-valid-purl\" f
33443344
Error: success criteria not met
33453345

33463346
---
3347+
3348+
[fetch OCI image manifest:stdout - 1]
3349+
{
3350+
"success": true,
3351+
"components": [
3352+
{
3353+
"name": "Unnamed",
3354+
"containerImage": "${REGISTRY}/acceptance/oci-image-manifest@sha256:${REGISTRY_acceptance/oci-image-manifest:latest_DIGEST}",
3355+
"source": {},
3356+
"successes": [
3357+
{
3358+
"msg": "Pass",
3359+
"metadata": {
3360+
"code": "builtin.attestation.signature_check"
3361+
}
3362+
},
3363+
{
3364+
"msg": "Pass",
3365+
"metadata": {
3366+
"code": "builtin.attestation.syntax_check"
3367+
}
3368+
},
3369+
{
3370+
"msg": "Pass",
3371+
"metadata": {
3372+
"code": "builtin.image.signature_check"
3373+
}
3374+
},
3375+
{
3376+
"msg": "Pass",
3377+
"metadata": {
3378+
"code": "manifest.match"
3379+
}
3380+
}
3381+
],
3382+
"success": true,
3383+
"signatures": [
3384+
{
3385+
"keyid": "",
3386+
"sig": "${IMAGE_SIGNATURE_acceptance/oci-image-manifest}"
3387+
}
3388+
],
3389+
"attestations": [
3390+
{
3391+
"type": "https://in-toto.io/Statement/v0.1",
3392+
"predicateType": "https://slsa.dev/provenance/v0.2",
3393+
"predicateBuildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
3394+
"signatures": [
3395+
{
3396+
"keyid": "",
3397+
"sig": "${ATTESTATION_SIGNATURE_acceptance/oci-image-manifest}"
3398+
}
3399+
]
3400+
}
3401+
]
3402+
}
3403+
],
3404+
"key": "${known_PUBLIC_KEY_JSON}",
3405+
"policy": {
3406+
"sources": [
3407+
{
3408+
"policy": [
3409+
"git::https://${GITHOST}/git/oci-image-manifest-policy"
3410+
]
3411+
}
3412+
],
3413+
"rekorUrl": "${REKOR}",
3414+
"publicKey": "${known_PUBLIC_KEY}"
3415+
},
3416+
"ec-version": "${EC_VERSION}",
3417+
"effective-time": "${TIMESTAMP}"
3418+
}
3419+
---
3420+
3421+
[fetch OCI image manifest:stderr - 1]
3422+
3423+
---

features/validate_image.feature

+25
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,31 @@ Feature: evaluate enterprise contract
926926
Then the exit status should be 0
927927
Then the output should match the snapshot
928928

929+
Scenario: fetch OCI image manifest
930+
Given a key pair named "known"
931+
Given an image named "acceptance/oci-image-manifest"
932+
Given a valid image signature of "acceptance/oci-image-manifest" image signed by the "known" key
933+
Given a valid Rekor entry for image signature of "acceptance/oci-image-manifest"
934+
Given a valid attestation of "acceptance/oci-image-manifest" signed by the "known" key
935+
Given a valid Rekor entry for attestation of "acceptance/oci-image-manifest"
936+
Given a git repository named "oci-image-manifest-policy" with
937+
| main.rego | examples/oci_image_manifest.rego |
938+
Given policy configuration named "ec-policy" with specification
939+
"""
940+
{
941+
"sources": [
942+
{
943+
"policy": [
944+
"git::https://${GITHOST}/git/oci-image-manifest-policy"
945+
]
946+
}
947+
]
948+
}
949+
"""
950+
When ec command is run with "validate image --image ${REGISTRY}/acceptance/oci-image-manifest --policy acceptance/ec-policy --public-key ${known_PUBLIC_KEY} --rekor-url ${REKOR} --show-successes"
951+
Then the exit status should be 0
952+
Then the output should match the snapshot
953+
929954
Scenario: tracing and debug logging
930955
Given a key pair named "trace_debug"
931956
And an image named "acceptance/trace-debug"

0 commit comments

Comments
 (0)