Return VSA and rich errors from verification (#38)

* Start of richer results from verification

* Pull out VSA code from signing

* Expose attestation signing fns

* Add VSA test

* Notes for policy result

* Require separate policy for VSA creation

* Load test signing key from tests

* Return rich object from policy

* Add result object schema and fix tests

* Ensure example test runs

* Remove data.yaml files from mock policies

* Don't run example - TUF policy isn't compatible

* Add attestation to manifests for all subjects

* Ensure adding attestation doesn't touch statements

* Don't export sign function

* Remove attestations from VerificationResult

* Change bool to Outcome enum in result

* Use outputLayout directly

* Make clearer that Outcome strings are for VSA

* Return multiple SLSA levels from policy

* Fix unmarshalling of policy-id (#39)

* Rename function

* Rename policy.VerificationResult -> policy.Result

* Re-add test for canonical input

---------

Co-authored-by: James Carnegie <james.carnegie@docker.com>
Co-authored-by: James Carnegie <kipz@users.noreply.github.com>
This commit is contained in:
Jonny Stoten
2024-05-22 14:49:23 +01:00
committed by GitHub
parent 745eea09e8
commit 1a7897a052
33 changed files with 776 additions and 461 deletions

View File

@@ -0,0 +1,48 @@
package attest
import rego.v1
keys := [{
"id": "6b241993defaba26558c64f94a94303ce860e7ad9163d801495c91cf57197c75",
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZmicqYSY38DprGr42jU0V3ND0ROj\nzSRH1+yjsxhh0bi52Hh/DuOhrSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==\n-----END PUBLIC KEY-----",
"from": "2023-12-15T14:00:00Z",
"to": null,
# this key is still active
"status": "active",
"signing-format": "dssev1",
}]
atts := union({
attestations.attestation("https://slsa.dev/provenance/v0.2"),
attestations.attestation("https://spdx.dev/Document"),
})
statements contains s if {
some att in atts
s := attestations.verify_envelope(att, keys)
}
subjects contains subject if {
some statement in statements
some subject in statement.subject
}
violations contains v if {
v := {
"type": "missing_attestation",
"description": "Attestation missing for subject",
"attestation": null,
"details": {},
}
}
result := {
"success": false,
"violations": violations,
"summary": {
"subjects": subjects,
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
"verifier": "docker-official-images",
"policy_uri": "https://docker.com/official/policy/v0.1",
},
}

View File

@@ -8,11 +8,4 @@ policies:
id: test-images
description: Local test images
files:
- path: doi/data.yaml
- path: doi/policy.rego
mirrors:
- policy-id: test-images
mirror:
domains: [localhost:5001]
prefix: ""

View File

@@ -0,0 +1,39 @@
package attest
import rego.v1
keys := [{
"id": "6b241993defaba26558c64f94a94303ce860e7ad9163d801495c91cf57197c75",
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZmicqYSY38DprGr42jU0V3ND0ROj\nzSRH1+yjsxhh0bi52Hh/DuOhrSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==\n-----END PUBLIC KEY-----",
"from": "2023-12-15T14:00:00Z",
"to": null,
# this key is still active
"status": "active",
"signing-format": "dssev1",
}]
atts := union({
attestations.attestation("https://slsa.dev/provenance/v0.2"),
attestations.attestation("https://spdx.dev/Document"),
})
statements contains s if {
some att in atts
s := attestations.verify_envelope(att, keys)
}
subjects contains subject if {
some statement in statements
some subject in statement.subject
}
result := {
"success": true,
"violations": set(),
"summary": {
"subjects": subjects,
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
"verifier": "docker-official-images",
"policy_uri": "https://docker.com/official/policy/v0.1",
},
}

View File

@@ -0,0 +1,11 @@
# map repos to policies
version: v1
kind: policy-mapping
policies:
- origin:
domain: docker.io
prefix: library/
id: test-images
description: Local test images
files:
- path: doi/policy.rego

View File

@@ -1,58 +0,0 @@
config:
doi:
keys:
- id: "f6a29392b1c08891ff456100aa448b4f6bf9c315850e11cc0883fe9c3c4412db"
key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+XOm2uWjLJhpsJtHCFdGic26suOy
mCl2pBgCof+AHGFZFca40JL833OT+nRSZJRMPKBGibWqsjFrLdRCkOB7bA==
-----END PUBLIC KEY-----
from: "2024-01-01T00:00:00Z"
to: "2024-01-15T12:00:00Z"
# this key was rotated at a planned time
status: "rotated"
signing-format: "dssev1"
- id: "e6f4c70fbba21cbcac44915fff53fd2fdf90dd8849445795fe58014c2b5f8c64"
key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZSkTE3si/JkRbuLjaYraS3//YBnX
8KtEcgdYKZQPl2DnSl4gPsu3KiVeEBWp5GK06IoZlcBAL3NF0OsUUP+yVg==
-----END PUBLIC KEY-----
from: "2024-01-15T12:00:00Z"
to: "2024-01-15T14:00:00Z"
# this key was leaked at a known time, so it revoked from that time
# this behaves the same way as "rotated" but might give another failure message
status: "revoked"
signing-format: "dssev1"
- id: "d45980c5cf39a5e1bab9febe3f16c1c0820b97a8fd061b0064e54b0826e856e4"
key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafssq2x1EDQcKDZhuSrCOxWWl5D4
JBa9iDJYDnLZp9kPKvv4RnD4rz7Ucfmd0l/zzM45qT29fSBTlguKmnOA8A==
-----END PUBLIC KEY-----
# this key was leaked at an unknown time, so it's completely distrusted
distrust: true
status: "revoked"
signing-format: "dssev1"
- id: "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4"
key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN
9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==
-----END PUBLIC KEY-----
from: "2023-12-15T14:00:00Z"
to: null
# this key is still active
status: "active"
signing-format: "dssev1"
- id: "b281835e00059de24fb06bd6db06eb0e4a33d7bd7210d7027c209f14b19e812a"
key: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgE4Jz6FrLc3lp/YRlbuwOjK4n6ac
jVkSDAmFhi3Ir2Jy+cKeEB7iRPcLvBy9qoMZ9E93m1NdWY6KtDo+Qi52Rg==
-----END PUBLIC KEY-----
from: "2024-01-15T14:00:00Z"
to: null
# this key is still active
status: "active"
signing-format: "dssev1"

View File

@@ -1,49 +0,0 @@
package attest
import rego.v1
import data.config
splitDigest := split(input.digest, ":")
digestType := splitDigest[0]
digest := splitDigest[1]
allow if {
some env in attestations.attestation("https://slsa.dev/verification_summary/v0.1")
some statement in verified_statements(config.doi.keys, env)
}
verified_statements(keys, env) := statements if {
statements := {statement |
statement := attestations.verify_envelope(env, keys)
some subject in statement.subject
valid_subject(subject)
}
}
valid_subject(sub) if {
print("valid_subject")
print("sub.digest[digestType]:", sub.digest[digestType])
print("digest", digest)
sub.digest[digestType] == digest
print("digest matches")
valid_subject_name(sub.name)
}
valid_subject_name(name) if {
input.isCanonical
print("is canonical, ignoring name")
}
valid_subject_name(name) if {
not input.isCanonical
print("valid_subject_name...")
print("name:", name)
print("input.purl:", input.purl)
name == input.purl
print("name match")
}

View File

@@ -1,25 +0,0 @@
package attest
import rego.v1
config := {"keys": []}
envs := [{"env": "test"}]
purl := "pkg:docker/library/alpine:1.2.3"
statement := {"subject": [{"name": purl, "digest": {"sha256": "dea014f47cd49d694d3a68564eb9e6ae38a7ee9624fd52ec05ccbef3f3fab8a0"}}]}
input_digest := "sha256:dea014f47cd49d694d3a68564eb9e6ae38a7ee9624fd52ec05ccbef3f3fab8a0"
test_with_mock_data if {
allow with attestations.attestation as envs
with attestations.verify_envelope as statement
with input.digest as input_digest
with input.purl as purl
with input.canonical as false
}
layout_digest := "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"
outout_purl := "pkg:docker/test-image@test?platform=linux%2Famd64"
test_with_signed_oci_layout if {
allow with input.digest as layout_digest
with input.purl as outout_purl
with input.canonical as false
}

5
test/testdata/test-signing-key.pem vendored Normal file
View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKZEqmmd++eAY3bmPoBdY6nC2wLy4da2yeVZNKCp6Oj2oAoGCCqGSM49
AwEHoUQDQgAEZmicqYSY38DprGr42jU0V3ND0ROjzSRH1+yjsxhh0bi52Hh/DuOh
rSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==
-----END EC PRIVATE KEY-----