Skip to content

Commit

Permalink
Signing event: sign/backfill-testing (#45)
Browse files Browse the repository at this point in the history
* chore: backfill testing from live

* Update targets metadata for role testing

Signed-off-by: TUF-on-CI <41898282+github-actions[bot]@users.noreply.github.com>

* Signature from @kipz

Signed-off-by: James Carnegie <[email protected]>

* Signature from @jonnystoten

Signed-off-by: Jonny Stoten <[email protected]>

---------

Signed-off-by: TUF-on-CI <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: James Carnegie <[email protected]>
Signed-off-by: Jonny Stoten <[email protected]>
Co-authored-by: James Carnegie <[email protected]>
Co-authored-by: TUF-on-CI <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonny Stoten <[email protected]>
  • Loading branch information
4 people authored Sep 24, 2024
1 parent 9b758cf commit e4bb369
Show file tree
Hide file tree
Showing 5 changed files with 519 additions and 5 deletions.
34 changes: 29 additions & 5 deletions metadata/testing.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"signatures": [
{
"keyid": "6132f1f2dd14bf3e9ba1a8df4c8435a77d2fd57f4a99bbb699ae61f85907818e",
"sig": "3065023063afeb04a3b1edab69442506469956fd98d7d696a12bf44a391d72560b642ad55ecea91dedc26568d1a61fe9cb98531d023100e5e1c9fe067ee69a84fc173cacb4c3ba2bcd54c81569906f05c0843e0e67231cec9679075fb7c2b0b73d4b1b99e34d7d"
"sig": "3065023100aa9a7680499a5f6a862f0e4a0febc78327ae6fd6418934d29262e1ad0cf3f3fb43a1400e4e1b557b0e1b4bedaa9a3f78023007cb176d2178e0c09e12ce8ca08a362a6a123b365ed8d9a0c81911d22195a8e522210bc35079d12916590430c9e45769"
},
{
"keyid": "f2149d8b7c1ece56d87d81f27fa68b745efc841892b3acfa382ad7f611e612ec",
"sig": "30660231008f40ff5258971c959f7b6498462a72ec2a4a6076ce8eb31a97d9a016d38a36be9471176099d6b6feb7b54db2b86a22f402310089db9c8a83691e44dbeecf5e62c8f464f61c71f3dcafa0a4387e4ea78a2b798f849c6a9f2874aeaed438e74547554a5c"
"sig": ""
},
{
"keyid": "7f4720651538fe96ce3067befcea395eccd49aa1a509568122eaadb300b0f880",
Expand All @@ -26,22 +26,46 @@
},
{
"keyid": "da52a1fb41a0a1f71cba47032f1074a4dc380aef4f3c53553a95ce54e60942ee",
"sig": "30650230469c25851466863081eb32874909479437350cd789a819d8aa093ddf25341795a64d069f176001b73fcba9537646d39f02310084076fd3e45454a72e05b39124b864ef7659dccecdb170a9b6ead81b6865ee0d64847909570f7a5667a5e8bfa96a97f9"
"sig": "306502303c0f424ade4e5bfcfc7cb83fc5f6268acae6ec50e7a88a6fc4df8d0745999755ad365df16fc0b78e60d72d6e1a3720d8023100b47320ccecddc581b69ce65a134b5308f3cc6d86d6051cd06aa743bfaeda34dedb9ffa6498d701003f7d060d8a5e2e62"
}
],
"signed": {
"_type": "targets",
"expires": "2025-09-18T20:44:40Z",
"expires": "2025-09-24T09:23:59Z",
"spec_version": "1.0.31",
"targets": {
"testing/doi/policy-full.rego": {
"hashes": {
"sha256": "b8e1b940cce51ecef546a96d202cbfe000c40441a3869e5cc5790b27d35bce65"
},
"length": 7972
},
"testing/doi/policy-vsa.rego": {
"hashes": {
"sha256": "c154c5cfc97c689534c1399e75eea72208a5cb04abd38f5627005db0742d4b38"
},
"length": 6114
},
"testing/mapping.yaml": {
"hashes": {
"sha256": "5b10a0858bf3446c4650abb938d989e4400f738400a626cba01b04eef1e6985b"
},
"length": 398
},
"testing/rekor/c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d.pem": {
"hashes": {
"sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd"
},
"length": 178
},
"testing/version-constraints": {
"hashes": {
"sha256": "be976ac935d635fb38588d36cbd9cf159ed1a0176779737c687439b567e609b0"
},
"length": 12
}
},
"version": 2,
"version": 3,
"x-tuf-on-ci-expiry-period": 365,
"x-tuf-on-ci-signing-period": 60
}
Expand Down
271 changes: 271 additions & 0 deletions targets/testing/doi/policy-full.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# doi/policy-full.rego verifies and validates the provenance and SBOM attestations attached to the image
package attest

import rego.v1

split_digest := split(input.digest, ":")

digest_type := split_digest[0]

digest := split_digest[1]

keys := [{
"id": "11681ba744a6b4efa85132e884e56a6e6aa6dcde123fbc4e79fd3fb2e1cf186b", # production
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFwhMAaawBNowyj/w35RtAqeWllCe\nKt83A6nxMnQfVYFEHTWPj9EvgV03ogMy63i/9Hfi2lWihO+4g2vzSS02Gg==\n-----END PUBLIC KEY-----",
"from": "2023-05-28T19:25:00Z",
"to": null,
"status": "active",
"distrust": false,
"signing-format": "dssev1",
}]

verify_opts := {"keys": keys}

verify_attestation(att) := attest.verify(att, verify_opts)

provenance_attestations contains att if {
result := attest.fetch("https://slsa.dev/provenance/v0.2")
not result.error
some att in result.value
}

provenance_signed_statements contains statement if {
some att in provenance_attestations
result := verify_attestation(att)
not result.error
statement := result.value
}

provenance_statements_with_subject contains statement if {
some statement in provenance_signed_statements
some subject in statement.subject
subject.digest[digest_type] == digest
valid_subject_name(input.isCanonical, subject.name, input.purl)
}

provenance_subjects contains subject if {
some statement in provenance_statements_with_subject
some subject in statement.subject
}

# we need to key this by statement_id rather than statement because we can't
# use an object as a key due to a bug(?) in OPA: https://github.com/open-policy-agent/opa/issues/6736
provenance_statement_violations[statement_id] contains v if {
some att in provenance_attestations
result := verify_attestation(att)
err := result.error
statement := unsafe_statement_from_attestation(att)
statement_id := id(statement)
v := {
"type": "unsigned_statement",
"description": sprintf("Statement is not correctly signed: %v", [err]),
"attestation": statement,
"details": {"error": err},
}
}

provenance_statement_violations[statement_id] contains v if {
some statement in provenance_signed_statements
statement_id := id(statement)
not statement in provenance_statements_with_subject
v := {
"type": "bad_subjects",
"description": "provenance statement does not have this image as a subject",
"attestation": statement,
"details": {"input": input},
}
}

provenance_statement_violations[statement_id] contains v if {
some statement in provenance_statements_with_subject
statement_id := id(statement)
v := field_value_does_not_equal(statement, "buildType", "https://mobyproject.org/buildkit@v1", "wrong_build_type")
}

provenance_statement_violations[statement_id] contains v if {
some statement in provenance_statements_with_subject
statement_id := id(statement)
v := field_value_does_not_equal(statement, "metadata.completeness.materials", true, "incomplete_materials")
}

bad_provenance_statements contains statement if {
some statement in provenance_statements_with_subject
statement_id := id(statement)
provenance_statement_violations[statement_id]
}

good_provenance_statements := provenance_statements_with_subject - bad_provenance_statements

sbom_attestations contains att if {
result := attest.fetch("https://spdx.dev/Document")
not result.error
some att in result.value
}

sbom_signed_statements contains statement if {
some att in sbom_attestations
result := verify_attestation(att)
not result.error
statement := result.value
}

sbom_statements_with_subject contains statement if {
some statement in sbom_signed_statements
some subject in statement.subject
subject.digest[digest_type] == digest
valid_subject_name(input.isCanonical, subject.name, input.purl)
}

sbom_subjects contains subject if {
some statement in sbom_statements_with_subject
some subject in statement.subject
}

# we need to key this by statement_id rather than statement because we can't
# use an object as a key due to a bug(?) in OPA: https://github.com/open-policy-agent/opa/issues/6736
sbom_statement_violations[statement_id] contains v if {
some att in sbom_attestations
result := verify_attestation(att)
err := result.error
statement := unsafe_statement_from_attestation(att)
statement_id := id(statement)
v := {
"type": "unsigned_statement",
"description": sprintf("Statement is not correctly signed: %v", [err]),
"attestation": statement,
"details": {"error": err},
}
}

sbom_statement_violations[statement_id] contains v if {
some statement in sbom_signed_statements
statement_id := id(statement)
not statement in sbom_statements_with_subject
v := {
"type": "bad_subjects",
"description": "SBOM statement does not have this image as a subject",
"attestation": statement,
"details": {"input": input},
}
}

sbom_statement_violations[statement_id] contains v if {
some statement in sbom_statements_with_subject
statement_id := id(statement)
v := field_value_does_not_equal(statement, "SPDXID", "SPDXRef-DOCUMENT", "wrong_spdx_id")
}

bad_sbom_statements contains statement if {
some statement in sbom_statements_with_subject
statement_id := id(statement)
sbom_statement_violations[statement_id]
}

good_sbom_statements := sbom_statements_with_subject - bad_sbom_statements

global_violations contains v if {
count(sbom_attestations) == 0
v := {
"type": "missing_attestation",
"description": "No https://slsa.dev/provenance/v0.2 attestation found",
"attestation": null,
"details": {},
}
}

global_violations contains v if {
count(provenance_attestations) == 0
v := {
"type": "missing_attestation",
"description": "No https://spdx.dev/Document attestation found",
"attestation": null,
"details": {},
}
}

all_violations contains v if {
some v in global_violations
}

all_violations contains v if {
some violations in sbom_statement_violations
some v in violations
}

all_violations contains v if {
some violations in provenance_statement_violations
some v in violations
}

subjects := union({sbom_subjects, provenance_subjects})

result := {
"success": allow,
"violations": all_violations,
"summary": {
"subjects": subjects,
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
"verifier": "docker-official-images",
"policy_uri": "https://docker.com/official/policy/v0.1",
},
}

default allow := false

allow if {
count(good_sbom_statements) > 0
count(good_provenance_statements) > 0
}

id(statement) := crypto.sha256(json.marshal(statement))

# TODO: this should take into account the repo name from the purl
valid_subject_name(true, name, purl)

valid_subject_name(false, name, purl) if {
name == purl
}

field_value_does_not_equal(statement, field, expected, type) := v if {
path := split(field, ".")
actual := object.get(statement.predicate, path, null)
expected != actual
v := is_not_violation(statement, field, expected, actual, type)
}

array_field_does_not_contain(statement, field, expected, type) := v if {
path := split(field, ".")
actual := object.get(statement.predicate, path, null)
not expected in actual
v := not_contains_violation(statement, field, expected, actual, type)
}

is_not_violation(statement, field, expected, actual, type) := {
"type": type,
"description": sprintf("%v is not %v", [field, expected]),
"attestation": statement,
"details": {
"field": field,
"actual": actual,
"expected": expected,
},
}

not_contains_violation(statement, field, expected, actual, type) := {
"type": type,
"description": sprintf("%v does not contain %v", [field, expected]),
"attestation": statement,
"details": {
"field": field,
"actual": actual,
"expected": expected,
},
}

# This is unsafe because we're not checking the signature on the attestation,
# do not call this unless you've already verified the attestation or you need the
# statement for some other reason
unsafe_statement_from_attestation(att) := statement if {
payload := att.payload
statement := json.unmarshal(base64.decode(payload))
}
Loading

0 comments on commit e4bb369

Please sign in to comment.