Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store the digest of each verified attestation in the PolicyAttestation object #925

Merged
merged 2 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/webhook/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@

PredicateType string
Payload []byte
Digest string
}

func attestationToPolicyAttestations(ctx context.Context, atts []attestation) []PolicyAttestation {
Expand Down Expand Up @@ -743,6 +744,7 @@
WorkflowRef: ce.GetCertExtensionGithubWorkflowRef(),
},
},
Digest: att.Digest,
PredicateType: att.PredicateType,
Payload: att.Payload,
})
Expand All @@ -754,6 +756,7 @@
},
PredicateType: att.PredicateType,
Payload: att.Payload,
Digest: att.Digest,

Check warning on line 759 in pkg/webhook/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/validator.go#L759

Added line #L759 was not covered by tests
})
}
}
Expand Down Expand Up @@ -890,6 +893,11 @@
// attestations and make sure that our particular one is satisfied.
checkedAttestations := make([]attestation, 0, len(verifiedAttestations))
for _, va := range verifiedAttestations {
attDigest, err := va.Digest()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the layer right? Just to make sure that with all the wonkiness for the OCI that we're getting the right one, that @wlynch had to make changes in here for:
#826

@wlynch could you take provide another set of 👀 here.

Copy link
Member

@wlynch wlynch Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be fine?

Digest (somewhat counterintuitively) is the SHA of the image layer.

For signatures this is problematic because because the signature bits are in the annotations, not the layer itself, so calling digest gets you the same value for different signatures.

But for attestations, the data is in the layer, so this might be fine? I'd spot check this with some real world examples first.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The code is using VerifyImageAttestations which is also used in the mono PR, and cosign tree to show the digest of the attestation of an image.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cosign tree ghcr.io/mattmoor/sbom-attestations/spdx-test@sha256:ba4037061b76ad8f306dd9e442877236015747ec42141caf504dc0df4d10708d
📦 Supply Chain Security Related artifacts for an image: ghcr.io/mattmoor/sbom-attestations/spdx-test@sha256:ba4037061b76ad8f306dd9e442877236015747ec42141caf504dc0df4d10708d
└── 💾 Attestations for an image tag: ghcr.io/mattmoor/sbom-attestations/spdx-test:sha256-ba4037061b76ad8f306dd9e442877236015747ec42141caf504dc0df4d10708d.att
   ├── 🍒 sha256:e9b75fb9a63666bd25c719a9fa3005ebd718e785d3a78844d5fcfd046e6bddc2
   ├── 🍒 sha256:f764a4251b2fe3c85dd46896b9d6e65361c9683755099d6dcd13009836d2e0e4
   └── 🍒 sha256:44726310314767412228d897a45943f158ef15a180270461b9f9847efa5c15de
└── 🔐 Signatures for an image tag: ghcr.io/mattmoor/sbom-attestations/spdx-test:sha256-ba4037061b76ad8f306dd9e442877236015747ec42141caf504dc0df4d10708d.sig
   └── 🍒 sha256:0f3404bb8c65cb8e1184c2d2fb3d1cec08771c1cd40c08f21b63cfaa96d13938

Found the att.Digest sha256:f764a4251b2fe3c85dd46896b9d6e65361c9683755099d6dcd13009836d2e0e4 for a spdx attestation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for checking!

if err != nil {
logging.FromContext(ctx).Errorf("failed to get the attestation digest for %s: %v", wantedAttestation.Name, err)
continue

Check warning on line 899 in pkg/webhook/validator.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/validator.go#L898-L899

Added lines #L898 - L899 were not covered by tests
}
attBytes, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, wantedAttestation.PredicateType, va)
if gotPredicateType != "" {
checkedPredicateTypes[gotPredicateType] = struct{}{}
Expand Down Expand Up @@ -920,12 +928,15 @@
continue
}
}

logging.FromContext(ctx).Debugf("found verified attestation with digest: %s", attDigest.String())
// Ok, so this passed aok, jot it down to our result set as
// verified attestation with the predicate type match
checkedAttestations = append(checkedAttestations, attestation{
Signature: va,
PredicateType: wantedAttestation.PredicateType,
Payload: attBytes,
Digest: attDigest.String(),
})
}
if len(checkedAttestations) == 0 {
Expand Down
3 changes: 3 additions & 0 deletions pkg/webhook/validator_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ type PolicyAttestation struct {
// not intended for consumption in the ClusterImagePolicy's outer policy
// block.
Payload []byte `json:"-"`

// Digest of the attestation
Digest string `json:"digest,omitempty"`
}

// GithubExtensions holds the Github-related OID extensions.
Expand Down
83 changes: 83 additions & 0 deletions pkg/webhook/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,7 @@ func TestValidatePolicy(t *testing.T) {
},
},
PredicateType: "vuln",
Digest: "sha256:01bd6aec99ad7c5d045d9aab649fd95b7af2b3b23887d34d7fce8b2e3c38ca0e",
Payload: []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://cosign.sigstore.dev/attestation/vuln/v1","subject":[{"name":"ghcr.io/distroless/static","digest":{"sha256":"a1e82f6a5f6dfc735165d3442e7cc5a615f72abac3db19452481f5f3c90fbfa8"}}],"predicate":{"invocation":{"parameters":null,"uri":"https://github.com/distroless/static/actions/runs/2757953139","event_id":"2757953139","builder.id":"Create Release"},"scanner":{"uri":"https://github.com/aquasecurity/trivy","version":"0.29.2","db":{"uri":"","version":""},"result":{"$schema":"https://json.schemastore.org/sarif-2.1.0-rtm.5.json","runs":[{"columnKind":"utf16CodeUnits","originalUriBaseIds":{"ROOTPATH":{"uri":"file:///"}},"results":[],"tool":{"driver":{"fullName":"Trivy Vulnerability Scanner","informationUri":"https://github.com/aquasecurity/trivy","name":"Trivy","rules":[],"version":"0.29.2"}}}],"version":"2.1.0"}},"metadata":{"scanStartedOn":"2022-07-29T02:28:42Z","scanFinishedOn":"2022-07-29T02:28:48Z"}}}`),
}},
},
Expand Down Expand Up @@ -1824,6 +1825,88 @@ func TestValidatePolicy(t *testing.T) {
}
}

func TestValidatePolicyAttestation(t *testing.T) {
// Resolved via crane digest on 2023/08/08
digestAtt := name.MustParseReference("ghcr.io/mattmoor/sbom-attestations/spdx-test@sha256:ba4037061b76ad8f306dd9e442877236015747ec42141caf504dc0df4d10708d")

attPayload := []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://spdx.dev/Document","subject":[{"name":"ghcr.io/chainguard-dev/log4shell-demo/app","digest":{"sha256":"ba4037061b76ad8f306dd9e442877236015747ec42141caf504dc0df4d10708d"}}],"predicate":{"Data":{"Reviews":[],"SPDXID":"SPDXRef-SPDXRef-DOCUMENT","annotations":[],"creationInfo":{"comment":"","created":"2022-06-08T15:31:05Z","creators":["Tool: spdx-maven-plugin"],"licenseListVersion":"3.5"},"dataLicense":"CC0-1.0","documentNamespace":"http://spdx.org/spdxpackages/log4shell-1.0-SNAPSHOT","files":[],"hasExtractedLicensingInfos":[],"name":"log4shell","packages":[{"Files":null,"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-4","annotations":null,"checksums":null,"comment":"This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file.","copyrightText":"UNSPECIFIED","downloadLocation":"NOASSERTION","licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"javax.servlet-api","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":""},"versionInfo":"4.0.1"},{"Files":null,"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-9","annotations":null,"checksums":null,"comment":"This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file.","copyrightText":"UNSPECIFIED","downloadLocation":"NOASSERTION","licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"log4j-api","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":""},"versionInfo":"2.14.1"},{"Files":null,"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-7","annotations":null,"checksums":null,"comment":"This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file.","copyrightText":"UNSPECIFIED","downloadLocation":"NOASSERTION","licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"deploy-jar","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":""},"versionInfo":"1.0"},{"Files":null,"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-6","annotations":null,"checksums":null,"comment":"This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file.","copyrightText":"UNSPECIFIED","downloadLocation":"NOASSERTION","licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"junit-jupiter-engine","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":""},"versionInfo":"5.7.1"},{"Files":null,"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-8","annotations":null,"checksums":null,"comment":"This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file.","copyrightText":"UNSPECIFIED","downloadLocation":"NOASSERTION","licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"log4j-core","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":""},"versionInfo":"2.14.1"},{"Files":null,"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-5","annotations":null,"checksums":null,"comment":"This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file.","copyrightText":"UNSPECIFIED","downloadLocation":"NOASSERTION","licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"junit-jupiter-api","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":""},"versionInfo":"5.7.1"},{"Files":[{"SPDXID":"SPDXRef-2","checksums":[{"algorithm":"SHA1","checksumValue":"9e58ba0426bed767f8da4d76afde1ee629d97c41"}],"copyrightText":"http://spdx.org/rdf/terms#noassertion","fileName":"./src/main/java/com/example/log4shell/log4j.java","fileTypes":["source"],"licenseConcluded":"NOASSERTION","licenseInfoInFiles":["NOASSERTION"]},{"SPDXID":"SPDXRef-3","checksums":[{"algorithm":"SHA1","checksumValue":"26df176b1904e473fddc8ca654bce5607b3fc64f"}],"copyrightText":"","fileName":"./src/main/java/com/example/log4shell/LoginServlet.java","fileTypes":["source"],"licenseConcluded":"NOASSERTION","licenseInfoInFiles":["NOASSERTION"]}],"IsFilesAnalyzedTagPresent":true,"IsUnpackaged":false,"SPDXID":"SPDXRef-1","annotations":null,"checksums":null,"copyrightText":"http://spdx.org/rdf/terms#noassertion","downloadLocation":"NOASSERTION","filesAnalyzed":true,"licenseConcluded":"NOASSERTION","licenseDeclared":"NOASSERTION","licenseInfoFromFiles":["NOASSERTION"],"name":"log4shell","packageFileName":"http://spdx.org/rdf/terms#noassertion","packageVerificationCode":{"packageVerificationCodeExcludedFiles":null,"packageVerificationCodeValue":"b5dabb87df1acb05636fe4dbc19afdfe18298a38"},"versionInfo":"1.0-SNAPSHOT"}],"relationships":[{"comment":"Relationship based on Maven POM file dependency information","relatedSpdxElement":"SPDXRef-4","relationshipType":"other","spdxElementId":"SPDXRef-1"},{"comment":"Relationship based on Maven POM file dependency information","relatedSpdxElement":"SPDXRef-9","relationshipType":"dynamicLink","spdxElementId":"SPDXRef-1"},{"comment":"Relationship based on Maven POM file dependency information","relatedSpdxElement":"SPDXRef-7","relationshipType":"other","spdxElementId":"SPDXRef-1"},{"relatedSpdxElement":"SPDXRef-1","relationshipType":"generates","spdxElementId":"SPDXRef-2"},{"comment":"Relationship based on Maven POM file dependency information","relatedSpdxElement":"SPDXRef-6","relationshipType":"testcaseOf","spdxElementId":"SPDXRef-1"},{"relatedSpdxElement":"SPDXRef-1","relationshipType":"generates","spdxElementId":"SPDXRef-3"},{"comment":"Relationship based on Maven POM file dependency information","relatedSpdxElement":"SPDXRef-8","relationshipType":"dynamicLink","spdxElementId":"SPDXRef-1"},{"comment":"Relationship based on Maven POM file dependency information","relatedSpdxElement":"SPDXRef-5","relationshipType":"testcaseOf","spdxElementId":"SPDXRef-1"},{"relatedSpdxElement":"SPDXRef-1","relationshipType":"describes","spdxElementId":"SPDXRef-DOCUMENT"}],"snippets":null,"spdxVersion":"SPDX-2.2"},"Timestamp":""}}`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, this is fine, just thinking that in the future for these large blobs might be better to read from a file, but like I said fine, done the same thing and every time I'm like 😁


fulcioURL, err := apis.ParseURL("https://fulcio.sigstore.dev")
if err != nil {
t.Fatalf("Failed to parse fake Fulcio URL")
}

tests := []struct {
name string
policy webhookcip.ClusterImagePolicy
want *PolicyResult
wantErrs []string
customContext context.Context
}{{
name: "simple test",
policy: webhookcip.ClusterImagePolicy{
Images: []v1alpha1.ImagePattern{{
Glob: "**",
}},
Authorities: []webhookcip.Authority{{
Name: "authority-0",
Keyless: &webhookcip.KeylessRef{
URL: fulcioURL,
Identities: []v1alpha1.Identity{{
IssuerRegExp: ".*",
SubjectRegExp: ".*",
}},
},
Attestations: []webhookcip.AttestationPolicy{{
Name: "test-att",
PredicateType: "https://spdx.dev/Document",
Type: "cue",
Data: `{"predicateType": "https://spdx.dev/Document"}`,
}},
},
},
},
want: &PolicyResult{
AuthorityMatches: map[string]AuthorityMatch{
"authority-0": {
Attestations: map[string][]PolicyAttestation{
"test-att": {{
PolicySignature: PolicySignature{
ID: "2906bbcbb40870d95b19e1bafe1db915ae73e5cd2ae1bdfee539ab6272ae7774",
Subject: "josh@dolit.ski",
Issuer: "https://github.com/login/oauth",
},
PredicateType: "https://spdx.dev/Document",
Digest: "sha256:f764a4251b2fe3c85dd46896b9d6e65361c9683755099d6dcd13009836d2e0e4",
Payload: attPayload,
}},
},
},
},
},
}}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cosignVerifySignatures = cosign.VerifyImageSignatures
cosignVerifyAttestations = cosign.VerifyImageAttestations
testContext := context.Background()

if test.customContext != nil {
testContext = test.customContext
}
kc, err := k8schain.NewNoClient(testContext)
if err != nil {
t.Fatalf("Failed to construct no client k8schain for testing")
}
got, gotErrs := ValidatePolicy(testContext, system.Namespace(), digestAtt, test.policy, kc)
validateErrors(t, test.wantErrs, gotErrs)
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("unexpected PolicyResult, %s with gotErrs %v", diff, gotErrs)
}
})
}
}
func validateErrors(t *testing.T, wantErr []string, got []error) {
t.Helper()
if len(wantErr) != len(got) {
Expand Down
Loading