From 22eb79a7f0c192dad5d69c890c0b8cf055b44af9 Mon Sep 17 00:00:00 2001 From: Jim Bugwadia Date: Fri, 12 Aug 2022 03:06:14 -0700 Subject: [PATCH] Fix PEM delimiter parse (#4331) * update log levels Signed-off-by: Jim Bugwadia * do not generate policy reports for blocked images Signed-off-by: Jim Bugwadia * fix PEM delimiter parsing and add test case Signed-off-by: Jim Bugwadia Signed-off-by: Jim Bugwadia --- pkg/cosign/cosign.go | 9 ++-- pkg/engine/imageVerify_test.go | 79 ++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 6db0cd7c87..266d36eecc 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -142,7 +142,7 @@ func buildCosignOptions(opts Options) (*cosign.CheckOpts, error) { } if opts.Key != "" { - if strings.HasPrefix(opts.Key, "-----BEGIN PUBLIC KEY-----") { + if strings.HasPrefix(strings.TrimSpace(opts.Key), "-----BEGIN PUBLIC KEY-----") { cosignOpts.SigVerifier, err = decodePEM([]byte(opts.Key)) if err != nil { return nil, errors.Wrap(err, "failed to load public key from PEM") @@ -426,14 +426,17 @@ func extractDigest(imgRef string, payload []payload.SimpleContainerImage) (strin if digest := p.Critical.Image.DockerManifestDigest; digest != "" { return digest, nil } else { - logger.Info("failed to extract image digest from verification response", "image", imgRef, "payload", p) - return "", fmt.Errorf("unknown image response for " + imgRef) + return "", fmt.Errorf("failed to extract image digest from signature payload for " + imgRef) } } return "", fmt.Errorf("digest not found for " + imgRef) } func matchCertificate(signatures []oci.Signature, subject, issuer string, extensions map[string]string) error { + if subject == "" && issuer == "" && len(extensions) == 0 { + return nil + } + for _, sig := range signatures { cert, err := sig.Cert() if err != nil { diff --git a/pkg/engine/imageVerify_test.go b/pkg/engine/imageVerify_test.go index 9372c829ed..53e68dec6e 100644 --- a/pkg/engine/imageVerify_test.go +++ b/pkg/engine/imageVerify_test.go @@ -132,15 +132,19 @@ var testResource = `{ } }` -var payloads = [][]byte{ +var attestationPayloads = [][]byte{ []byte(`{"payloadType":"https://example.com/CodeReview/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL0NvZGVSZXZpZXcvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9qaW1idWd3YWRpYS9wYXVzZTIiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjMxYmZiNGQwMjEzZjI1NGQzNjFlMDA3OWRlYWFlYmVmYTRmODJiYTdhYTc2ZWY4MmU5MGI0OTM1YWQ1YjEwNSJ9fV0sInByZWRpY2F0ZSI6eyJhdXRob3IiOiJtYWlsdG86YWxpY2VAZXhhbXBsZS5jb20iLCJyZXBvIjp7ImJyYW5jaCI6Im1haW4iLCJ0eXBlIjoiZ2l0IiwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2V4YW1wbGUvbXktcHJvamVjdCJ9LCJyZXZpZXdlcnMiOlsibWFpbHRvOmJvYkBleGFtcGxlLmNvbSJdfX0=","signatures":[{"keyid":"","sig":"MEYCIQCrEr+vgPDmNCrqGDE/4z9iMLmCXMXcDlGKtSoiuMTSFgIhAN2riBaGk4accWzVl7ypi1XTRxyrPYHst8DesugPXgOf"}]}`), []byte(`{"payloadType":"cosign.sigstore.dev/attestation/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vamltYnVnd2FkaWEvcGF1c2UyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImIzMWJmYjRkMDIxM2YyNTRkMzYxZTAwNzlkZWFhZWJlZmE0ZjgyYmE3YWE3NmVmODJlOTBiNDkzNWFkNWIxMDUifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImhlbGxvIVxuIiwiVGltZXN0YW1wIjoiMjAyMS0xMC0wNVQwNToxODoxMVoifX0=","signatures":[{"keyid":"","sig":"MEQCIF5r9lf55rnYNPByZ9v6bortww694UEPvmyBIelIDYbIAiBNTGX4V64Oj6jZVRpkJQRxdzKUPYqC5GZTb4oS6eQ6aQ=="}]}`), []byte(`{"payloadType":"https://example.com/CodeReview/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL0NvZGVSZXZpZXcvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9qaW1idWd3YWRpYS9wYXVzZTIiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjMxYmZiNGQwMjEzZjI1NGQzNjFlMDA3OWRlYWFlYmVmYTRmODJiYTdhYTc2ZWY4MmU5MGI0OTM1YWQ1YjEwNSJ9fV0sInByZWRpY2F0ZSI6eyJhdXRob3IiOiJtYWlsdG86YWxpY2VAZXhhbXBsZS5jb20iLCJyZXBvIjp7ImJyYW5jaCI6Im1haW4iLCJ0eXBlIjoiZ2l0IiwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2V4YW1wbGUvbXktcHJvamVjdCJ9LCJyZXZpZXdlcnMiOlsibWFpbHRvOmJvYkBleGFtcGxlLmNvbSJdfX0=","signatures":[{"keyid":"","sig":"MEUCIEeZbdBEFQzWqiMhB+SJgM6yFppUuQSKrpOIX1mxLDmRAiEA8pXqFq0GVc9LKhPzrnJRZhSruDNiKbiLHG5x7ETFyY8="}]}`), } +var signaturePayloads = [][]byte{ + []byte(`{"critical":{"identity":{"docker-reference":"ghcr.io/kyverno/test-verify-image"},"image":{"docker-manifest-digest":"sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105"},"type":"cosign container image signature"},"optional":null}`), +} + func Test_CosignMockAttest(t *testing.T) { policyContext := buildContext(t, testPolicyGood, testResource, "") - err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", payloads) + err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", attestationPayloads) assert.NilError(t, err) er, ivm := VerifyAndPatchImages(policyContext) @@ -152,7 +156,7 @@ func Test_CosignMockAttest(t *testing.T) { func Test_CosignMockAttest_fail(t *testing.T) { policyContext := buildContext(t, testPolicyBad, testResource, "") - err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", payloads) + err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", attestationPayloads) assert.NilError(t, err) er, _ := VerifyAndPatchImages(policyContext) @@ -579,7 +583,7 @@ func Test_MarkImageVerified(t *testing.T) { image := "ghcr.io/jimbugwadia/pause2:latest" cosign.ClearMock() policyContext := buildContext(t, testPolicyGood, testResource, "") - err := cosign.SetMock(image, payloads) + err := cosign.SetMock(image, attestationPayloads) assert.NilError(t, err) engineResponse, verifiedImages := VerifyAndPatchImages(policyContext) @@ -618,3 +622,70 @@ func applyPatches(t *testing.T, patches [][]byte) unstructured.Unstructured { assert.NilError(t, err) return u } + +func Test_ParsePEMDelimited(t *testing.T) { + testPEMPolicy := `{ + "apiVersion": "kyverno.io/v1", + "kind": "Policy", + "metadata": { + "name": "check-image" + }, + "spec": { + "validationFailureAction": "enforce", + "background": false, + "webhookTimeoutSeconds": 30, + "failurePolicy": "Fail", + "rules": [ + { + "name": "check-image", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "verifyImages": [ + { + "imageReferences": [ + "*" + ], + "attestors": [ + { + "count": 1, + "entries": [ + { + "keys": { + "publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfVMHGmFK4OgVqhy36KZ7a3r4R4/o\nCwaCVvXZV4ZULFbkFZ0IodGqKqcVmgycnoj7d8TpKpAUVNF8kKh90ewH3A==\n-----END PUBLIC KEY-----\n-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0f1W0XigyPFbX8Xq3QmkbL9gDFTf\nRfc8jF7UadBcwKxiyvPSOKZn+igQfXzpNjrwPSZ58JGvF4Fs8BB3fSRP2g==\n-----END PUBLIC KEY-----" + } + } + ] + } + ] + } + ] + } + ] + } + }` + + image := "ghcr.io/jimbugwadia/pause2:latest" + cosign.ClearMock() + policyContext := buildContext(t, testPEMPolicy, testResource, "") + err := cosign.SetMock(image, signaturePayloads) + assert.NilError(t, err) + + engineResponse, verifiedImages := VerifyAndPatchImages(policyContext) + assert.Assert(t, engineResponse != nil) + assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1) + assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status, response.RuleStatusPass) + + assert.Assert(t, verifiedImages != nil) + assert.Assert(t, verifiedImages.Data != nil) + assert.Equal(t, len(verifiedImages.Data), 1) + assert.Equal(t, verifiedImages.isVerified(image), true) +}