From 7d3a12335f81a4616ccf9c2ca5c3963925412962 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 12 Apr 2022 09:06:20 -0700 Subject: [PATCH 1/3] Refactor policy related code, add support for vuln verify Signed-off-by: Ville Aikas --- .../workflows/kind-verify-attestation.yaml | 51 ++++- cmd/cosign/cli/verify/verify_attestation.go | 82 +------- pkg/cosign/attestation/attestation.go | 9 + pkg/policy/attestation.go | 130 ++++++++++++ pkg/policy/attestation_test.go | 188 ++++++++++++++++++ pkg/policy/testdata/valid/custom | 1 + pkg/policy/testdata/valid/vuln | 1 + .../testdata/attestations/vuln-predicate.json | 21 ++ test/testdata/policies/cue-vuln-fails.cue | 25 +++ test/testdata/policies/cue-vuln-works.cue | 22 ++ 10 files changed, 445 insertions(+), 85 deletions(-) create mode 100644 pkg/policy/attestation.go create mode 100644 pkg/policy/attestation_test.go create mode 100644 pkg/policy/testdata/valid/custom create mode 100644 pkg/policy/testdata/valid/vuln create mode 100644 test/testdata/attestations/vuln-predicate.json create mode 100644 test/testdata/policies/cue-vuln-fails.cue create mode 100644 test/testdata/policies/cue-vuln-works.cue diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml index 553313d04ce..d6278c7400e 100644 --- a/.github/workflows/kind-verify-attestation.yaml +++ b/.github/workflows/kind-verify-attestation.yaml @@ -45,7 +45,10 @@ jobs: GO111MODULE: on GOFLAGS: -ldflags=-s -ldflags=-w KOCACHE: ~/ko - COSIGN_EXPERIMENTAL: true + # Trust the custom Rekor API endpoint for fetching the Public Key from it. + SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY: "true" + # We are only testing keyless here, so set it. + COSIGN_EXPERIMENTAL: "true" steps: - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v2.4.0 @@ -89,28 +92,28 @@ jobs: - name: Create attestation for it run: | echo -n 'foobar e2e test' > ./predicate-file - SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY=1 COSIGN_EXPERIMENTAL=1 ./cosign attest --predicate ./predicate-file --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --force ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }} + ./cosign attest --predicate ./predicate-file --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --force ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }} - name: Verify with cosign run: | - SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY=1 COSIGN_EXPERIMENTAL=1 ./cosign verify --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} + ./cosign verify --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} - - name: Verify attestation with cosign, works + - name: Verify custom attestation with cosign, works run: | - echo '::group:: test verify-attestation success' - if ! SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY=1 COSIGN_EXPERIMENTAL=1 ./cosign verify-attestation --policy ./test/testdata/policies/cue-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + echo '::group:: test custom verify-attestation success' + if ! ./cosign verify-attestation --policy ./test/testdata/policies/cue-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then echo Failed to verify attestation with a valid policy exit 1 else - echo Successfully validated attestation with a valid policy + echo Successfully validated custom attestation with a valid policy fi echo '::endgroup::' - - name: Verify attestation with cosign, fails + - name: Verify custom attestation with cosign, fails run: | - echo '::group:: test verify-attestation success' - if SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY=1 COSIGN_EXPERIMENTAL=1 ./cosign verify-attestation --policy ./test/testdata/policies/cue-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then - echo verify-attestation succeeded with cue policy that should not work + echo '::group:: test custom verify-attestation success' + if ./cosign verify-attestation --policy ./test/testdata/policies/cue-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + echo custom verify-attestation succeeded with cue policy that should not work exit 1 else echo Successfully failed a policy that should not work @@ -120,3 +123,29 @@ jobs: - name: Collect diagnostics if: ${{ failure() }} uses: chainguard-dev/actions/kind-diag@84c993eaf02da1c325854fb272a4df9184bd80fc # main + + - name: Create vuln attestation for it + run: | + ./cosign attest --predicate ./test/testdata/attestations/vuln-predicate.json --type vuln --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --force ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }} + + - name: Verify vuln attestation with cosign, works + run: | + echo '::group:: test vuln verify-attestation success' + if ! ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + echo Failed to verify attestation with a valid policy + exit 1 + else + echo Successfully validated vuln attestation with a valid policy + fi + echo '::endgroup::' + + - name: Verify custom attestation with cosign, fails + run: | + echo '::group:: test vuln verify-attestation success' + if ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then + echo verify-attestation succeeded with cue policy that should not work + exit 1 + else + echo Successfully failed a policy that should not work + fi + echo '::endgroup::' diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index e868eb8fb11..87747f54d89 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -18,15 +18,13 @@ package verify import ( "context" "crypto" - "encoding/base64" - "encoding/json" + "crypto/ecdsa" "flag" "fmt" "os" "path/filepath" "github.com/google/go-containerregistry/pkg/name" - "github.com/in-toto/in-toto-golang/in_toto" "github.com/pkg/errors" "github.com/sigstore/cosign/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/pkg/cosign/rego" @@ -39,6 +37,7 @@ import ( "github.com/sigstore/cosign/pkg/cosign" "github.com/sigstore/cosign/pkg/cosign/cue" "github.com/sigstore/cosign/pkg/cosign/pivkey" + "github.com/sigstore/cosign/pkg/policy" sigs "github.com/sigstore/cosign/pkg/signature" ) @@ -47,11 +46,11 @@ import ( type VerifyAttestationCommand struct { options.RegistryOptions CheckClaims bool + KeyRef string CertRef string CertEmail string CertOidcIssuer string CertChain string - KeyRef string Sk bool Slot string Output string @@ -122,7 +121,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return errors.Wrap(err, "loading certificate from reference") } if c.CertChain == "" { - co.SigVerifier, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256) + co.SigVerifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256) if err != nil { return errors.Wrap(err, "creating certificate verifier") } @@ -182,80 +181,15 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e var validationErrors []error for _, vp := range verified { - var payloadData map[string]interface{} - - p, err := vp.Payload() - if err != nil { - return errors.Wrap(err, "could not get payload") - } - - err = json.Unmarshal(p, &payloadData) + payload, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, vp) if err != nil { - return errors.Wrap(err, "unmarshal payload data") + return errors.Wrap(err, "converting to consumable policy validation") } - - var decodedPayload []byte - if val, ok := payloadData["payload"]; ok { - decodedPayload, err = base64.StdEncoding.DecodeString(val.(string)) - if err != nil { - return fmt.Errorf("could not decode 'payload': %w", err) - } - } else { - return fmt.Errorf("could not find 'payload' in payload data") - } - - predicateURI, ok := options.PredicateTypeMap[c.PredicateType] - if !ok { - return fmt.Errorf("invalid predicate type: %s", c.PredicateType) - } - - // Only apply the policy against the requested predicate type - var statement in_toto.Statement - if err := json.Unmarshal(decodedPayload, &statement); err != nil { - return fmt.Errorf("unmarshal in-toto statement: %w", err) - } - if statement.PredicateType != predicateURI { + if len(payload) == 0 { + // This is not the predicate type we're looking for. continue } - var payload []byte - switch c.PredicateType { - case options.PredicateCustom: - payload, err = json.Marshal(statement) - if err != nil { - return fmt.Errorf("error when generating CosignStatement: %w", err) - } - case options.PredicateLink: - var linkStatement in_toto.LinkStatement - if err := json.Unmarshal(decodedPayload, &linkStatement); err != nil { - return fmt.Errorf("unmarshal LinkStatement: %w", err) - } - payload, err = json.Marshal(linkStatement) - if err != nil { - return fmt.Errorf("error when generating LinkStatement: %w", err) - } - case options.PredicateSLSA: - var slsaProvenanceStatement in_toto.ProvenanceStatement - if err := json.Unmarshal(decodedPayload, &slsaProvenanceStatement); err != nil { - return fmt.Errorf("unmarshal ProvenanceStatement: %w", err) - } - payload, err = json.Marshal(slsaProvenanceStatement) - if err != nil { - return fmt.Errorf("error when generating ProvenanceStatement: %w", err) - } - case options.PredicateSPDX: - var spdxStatement in_toto.SPDXStatement - if err := json.Unmarshal(decodedPayload, &spdxStatement); err != nil { - return fmt.Errorf("unmarshal SPDXStatement: %w", err) - } - payload, err = json.Marshal(spdxStatement) - if err != nil { - return fmt.Errorf("error when generating SPDXStatement: %w", err) - } - default: - return fmt.Errorf("unsupported predicate type: %s", c.PredicateType) - } - if len(cuePolicies) > 0 { fmt.Fprintf(os.Stderr, "will be validating against CUE policies: %v\n", cuePolicies) cueValidationErr := cue.ValidateJSON(payload, cuePolicies) diff --git a/pkg/cosign/attestation/attestation.go b/pkg/cosign/attestation/attestation.go index ce113220695..3215c32ca87 100644 --- a/pkg/cosign/attestation/attestation.go +++ b/pkg/cosign/attestation/attestation.go @@ -50,6 +50,15 @@ type CosignVulnPredicate struct { Metadata Metadata `json:"metadata"` } +// I think this will be moving to upstream in-toto in the fullness of time +// but creating it here for now so that we have a way to deserialize it +// as a InToto Statement +// https://github.com/in-toto/attestation/issues/58 +type CosignVulnStatement struct { + in_toto.StatementHeader + Predicate CosignVulnPredicate `json:"predicate"` +} + type Invocation struct { Parameters interface{} `json:"parameters"` URI string `json:"uri"` diff --git a/pkg/policy/attestation.go b/pkg/policy/attestation.go new file mode 100644 index 00000000000..44808201283 --- /dev/null +++ b/pkg/policy/attestation.go @@ -0,0 +1,130 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/pkg/errors" + "github.com/sigstore/cosign/pkg/oci" + + "github.com/sigstore/cosign/cmd/cosign/cli/options" + "github.com/sigstore/cosign/pkg/cosign/attestation" +) + +// AttestationToPayloadJSON takes in a verified Attestation (oci.Signature) and +// marshals it into a JSON depending on the payload that's then consumable +// by policy engine like cue, rego, etc. +// +// Anything fed here must have been validated with either +// `VerifyLocalImageAttestations` or `VerifyImageAttestations` +// +// If there's no error, and payload is empty means the predicateType did not +// match the attestation. +func AttestationToPayloadJSON(ctx context.Context, predicateType string, verifiedAttestation oci.Signature) ([]byte, error) { + // Check the predicate up front, no point in wasting time if it's invalid. + predicateURI, ok := options.PredicateTypeMap[predicateType] + if !ok { + return nil, fmt.Errorf("invalid predicate type: %s", predicateType) + } + + var payloadData map[string]interface{} + + p, err := verifiedAttestation.Payload() + if err != nil { + return nil, errors.Wrap(err, "getting payload") + } + + err = json.Unmarshal(p, &payloadData) + if err != nil { + return nil, errors.Wrap(err, "unmarshaling payload data") + } + + var decodedPayload []byte + if val, ok := payloadData["payload"]; ok { + decodedPayload, err = base64.StdEncoding.DecodeString(val.(string)) + if err != nil { + return nil, errors.Wrap(err, "decoding payload") + } + } else { + return nil, fmt.Errorf("could not find payload in payload data") + } + + // Only apply the policy against the requested predicate type + var statement in_toto.Statement + if err := json.Unmarshal(decodedPayload, &statement); err != nil { + return nil, fmt.Errorf("unmarshal in-toto statement: %w", err) + } + if statement.PredicateType != predicateURI { + // This is not the predicate we're looking for, so skip it. + return nil, nil + } + + // NB: In many (all?) of these cases, we could just return the + // 'json.Marshal', but we check for errors here to decorate them + // with more meaningful error message. + var payload []byte + switch predicateType { + case options.PredicateCustom: + payload, err = json.Marshal(statement) + if err != nil { + return nil, errors.Wrap(err, "generating CosignStatement") + } + case options.PredicateLink: + var linkStatement in_toto.LinkStatement + if err := json.Unmarshal(decodedPayload, &linkStatement); err != nil { + return nil, errors.Wrap(err, "unmarshaling LinkStatement") + } + payload, err = json.Marshal(linkStatement) + if err != nil { + return nil, errors.Wrap(err, "marshaling LinkStatement") + } + case options.PredicateSLSA: + var slsaProvenanceStatement in_toto.ProvenanceStatement + if err := json.Unmarshal(decodedPayload, &slsaProvenanceStatement); err != nil { + return nil, errors.Wrap(err, "unmarshaling ProvenanceStatement") + } + payload, err = json.Marshal(slsaProvenanceStatement) + if err != nil { + return nil, errors.Wrap(err, "marshaling ProvenanceStatement") + } + case options.PredicateSPDX: + var spdxStatement in_toto.SPDXStatement + if err := json.Unmarshal(decodedPayload, &spdxStatement); err != nil { + return nil, errors.Wrap(err, "unmarshaling SPDXStatement") + } + payload, err = json.Marshal(spdxStatement) + if err != nil { + return nil, errors.Wrap(err, "marshaling SPDXStatement") + } + case options.PredicateVuln: + var vulnStatement attestation.CosignVulnStatement + if err := json.Unmarshal(decodedPayload, &vulnStatement); err != nil { + return nil, errors.Wrap(err, "unmarshaling CosignVulnStatement") + } + payload, err = json.Marshal(vulnStatement) + if err != nil { + return nil, errors.Wrap(err, "marshaling CosignVulnStatement") + } + default: + return nil, fmt.Errorf("unsupported predicate type: %s", predicateType) + } + return payload, nil +} diff --git a/pkg/policy/attestation_test.go b/pkg/policy/attestation_test.go new file mode 100644 index 00000000000..c79fb4babda --- /dev/null +++ b/pkg/policy/attestation_test.go @@ -0,0 +1,188 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package policy + +import ( + "context" + "crypto/x509" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "strings" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/sigstore/cosign/pkg/cosign/attestation" + "github.com/sigstore/cosign/pkg/cosign/bundle" + "github.com/sigstore/cosign/pkg/oci" + "github.com/sigstore/cosign/pkg/oci/static" +) + +type failingAttestation struct { +} + +func (fa *failingAttestation) Payload() ([]byte, error) { + return nil, fmt.Errorf("inducing test failure") +} +func (fa *failingAttestation) Annotations() (map[string]string, error) { + return nil, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Base64Signature() (string, error) { + return "", fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Cert() (*x509.Certificate, error) { + return nil, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Chain() ([]*x509.Certificate, error) { + return nil, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Bundle() (*bundle.RekorBundle, error) { + return nil, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Digest() (v1.Hash, error) { + return v1.Hash{}, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) DiffID() (v1.Hash, error) { + return v1.Hash{}, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Compressed() (io.ReadCloser, error) { + return nil, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Uncompressed() (io.ReadCloser, error) { + return nil, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) Size() (int64, error) { + return 0, fmt.Errorf("unimplemented") +} +func (fa *failingAttestation) MediaType() (types.MediaType, error) { + return types.DockerConfigJSON, fmt.Errorf("unimplemented") +} + +var _ oci.Signature = (*failingAttestation)(nil) + +const ( + // Result of "echo 'nottotostatement' | base64" + // invalidTotoStatement = "bm90dG90b3N0YXRlbWVudAo=" + invalidTotoStatement = `{"payloadType":"application/vnd.in-toto+json","payload":"bm90dG90b3N0YXRlbWVudAo"}` +) + +func checkFailure(t *testing.T, want string, err error) { + t.Helper() + if err == nil { + t.Fatalf("Expected error, got none") + } + if !strings.Contains(err.Error(), want) { + t.Errorf("Failed to get the expected error of %q, got: %s", want, err) + } +} + +func TestFailures(t *testing.T) { + tests := []struct { + payload string + predicateType string + wantErrSubstring string + }{{payload: "", predicateType: "notvalidpredicate", wantErrSubstring: "invalid predicate type"}, + {payload: "", wantErrSubstring: "unmarshaling payload data"}, {payload: "{badness", wantErrSubstring: "unmarshaling payload data"}, + {payload: `{"payloadType":"notmarshallable}`, wantErrSubstring: "unmarshaling payload data"}, + {payload: `{"payload":"shou!ln'twork"}`, wantErrSubstring: "decoding payload"}, + {payload: `{"payloadType":"finebutnopayload"}`, wantErrSubstring: "could not find payload"}, + {payload: invalidTotoStatement, wantErrSubstring: "decoding payload: illegal base64"}, + } + for _, tc := range tests { + att, err := static.NewSignature([]byte(tc.payload), "") + if err != nil { + t.Fatal("Failed to create static.NewSignature: ", err) + } + predicateType := tc.predicateType + if predicateType == "" { + predicateType = "custom" + } + _, err = AttestationToPayloadJSON(context.TODO(), predicateType, att) + checkFailure(t, tc.wantErrSubstring, err) + } +} + +// TestMalformedPayload tests various non-predicate specific failures that +// are done even before we start processing the payload. +// This just stands alone since didn't want to complicate above tests with +// constructing different attestations there. +func TestErroringPayload(t *testing.T) { + // Payload() call fails + _, err := AttestationToPayloadJSON(context.TODO(), "custom", &failingAttestation{}) + checkFailure(t, "inducing test failure", err) +} +func TestAttestationToPayloadJson(t *testing.T) { + dir := "valid" + files := getDirFiles(t, dir) + for _, fileName := range files { + bytes := readAttestationFromTestFile(t, dir, fileName) + ociSig, err := static.NewSignature(bytes, "") + if err != nil { + t.Fatal("Failed to create static.NewSignature: ", err) + } + jsonBytes, err := AttestationToPayloadJSON(context.TODO(), fileName, ociSig) + if err != nil { + t.Fatalf("Failed to convert : %s", err) + } + switch fileName { + case "custom": + var intoto in_toto.Statement + if err := json.Unmarshal(jsonBytes, &intoto); err != nil { + t.Fatal("Wanted custom statement, can't unmarshal to it: ", err) + } + checkPredicateType(t, attestation.CosignCustomProvenanceV01, intoto.PredicateType) + case "vuln": + var vulnStatement attestation.CosignVulnStatement + if err := json.Unmarshal(jsonBytes, &vulnStatement); err != nil { + t.Fatal("Wanted vuln statement, can't unmarshal to it: ", err) + } + checkPredicateType(t, attestation.CosignVulnProvenanceV01, vulnStatement.PredicateType) + case "default": + t.Fatal("non supported predicate file") + } + } +} + +func checkPredicateType(t *testing.T, want, got string) { + t.Helper() + if want != got { + t.Errorf("Did not get expected predicateType, want: %s got: %s", want, got) + } +} + +func readAttestationFromTestFile(t *testing.T, dir, name string) []byte { + t.Helper() + b, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s/%s", dir, name)) + if err != nil { + t.Fatalf("Failed to read file : %s ReadFile() = %s", name, err) + } + return b +} + +func getDirFiles(t *testing.T, dir string) []string { + files, err := ioutil.ReadDir(fmt.Sprintf("testdata/%s", dir)) + if err != nil { + t.Fatalf("Failed to read dir : %s ReadFile() = %s", dir, err) + } + ret := []string{} + for _, file := range files { + ret = append(ret, file.Name()) + } + return ret +} diff --git a/pkg/policy/testdata/valid/custom b/pkg/policy/testdata/valid/custom new file mode 100644 index 00000000000..d05cabaa931 --- /dev/null +++ b/pkg/policy/testdata/valid/custom @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6InJlZ2lzdHJ5LmxvY2FsOjUwMDAva25hdGl2ZS9kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjZjNmZkNmE0MTE1YzZlOTk4ZmYzNTdjZDkxNDY4MDkzMWJiOWE2YzFhN2NkNWY1Y2IyZjVlMWMwOTMyYWI2ZWQifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb2JhciB0ZXN0IGF0dGVzdGF0aW9uIiwiVGltZXN0YW1wIjoiMjAyMi0wNC0wN1QxOToyMjoyNVoifX0=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]} diff --git a/pkg/policy/testdata/valid/vuln b/pkg/policy/testdata/valid/vuln new file mode 100644 index 00000000000..2a38ba9d81c --- /dev/null +++ b/pkg/policy/testdata/valid/vuln @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3Z1bG4vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicmVnaXN0cnkubG9jYWw6NTAwMC9rbmF0aXZlL2RlbW8iLCJkaWdlc3QiOnsic2hhMjU2IjoiM2MxOWFhOTgwYTljNTcwOWEyYzk2YzJkMDc3OWZlYmY2ZTVlNDUzYjkyYjE3MmNlODRjYjg1ZmRhZjY5NTM3MyJ9fV0sInByZWRpY2F0ZSI6eyJpbnZvY2F0aW9uIjp7InBhcmFtZXRlcnMiOm51bGwsInVyaSI6IiIsImV2ZW50X2lkIjoiIiwiYnVpbGRlci5pZCI6IiJ9LCJzY2FubmVyIjp7InVyaSI6IiIsInZlcnNpb24iOiIiLCJkYiI6eyJ1cmkiOiIiLCJ2ZXJzaW9uIjoiIn0sInJlc3VsdCI6bnVsbH0sIm1ldGFkYXRhIjp7InNjYW5TdGFydGVkT24iOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsInNjYW5GaW5pc2hlZE9uIjoiMDAwMS0wMS0wMVQwMDowMDowMFoifX19","signatures":[{"keyid":"","sig":"MEUCIHE9QkUy+d6uFwae0LSH2Fgy99na3jQvaYMU6qj5dzbFAiEA0uKmqGY1ZHoQZsd0BR4Ug0c8d+sHT0hPcxA61o4DKlM="}]} diff --git a/test/testdata/attestations/vuln-predicate.json b/test/testdata/attestations/vuln-predicate.json new file mode 100644 index 00000000000..a6b351cb0ab --- /dev/null +++ b/test/testdata/attestations/vuln-predicate.json @@ -0,0 +1,21 @@ +{ + "invocation": { + "parameters": null, + "uri": "invocation.example.com/cosign-testing", + "event_id": "", + "builder.id": "" + }, + "scanner": { + "uri": "fakescanner.example.com/cosign-testing", + "version": "", + "db": { + "uri": "", + "version": "" + }, + "result": null + }, + "metadata": { + "scanStartedOn": "2022-04-12T00:00:00Z", + "scanFinishedOn": "2022-04-12T00:10:00Z" + } +} diff --git a/test/testdata/policies/cue-vuln-fails.cue b/test/testdata/policies/cue-vuln-fails.cue new file mode 100644 index 00000000000..57d741abe00 --- /dev/null +++ b/test/testdata/policies/cue-vuln-fails.cue @@ -0,0 +1,25 @@ +import "time" + +// This is after our scan happened +before: time.Parse(time.RFC3339, "2022-04-01T17:10:27Z") +after: time.Parse(time.RFC3339, "2022-03-09T17:10:27Z") + +// The predicateType field must match this string +predicateType: "cosign.sigstore.dev/attestation/vuln/v1" + +predicate: { + invocation: { + // This is the wrong invocation uri + uri: "invocation.example.com/cosign-testing-invalid" + } + scanner: { + // This is the wrong scanner uri + uri: "fakescanner.example.com/cosign-testing-invalid" + } + metadata: { + scanStartedOn: after + scanFinishedOn: after + } +} diff --git a/test/testdata/policies/cue-vuln-works.cue b/test/testdata/policies/cue-vuln-works.cue new file mode 100644 index 00000000000..79ea71080d9 --- /dev/null +++ b/test/testdata/policies/cue-vuln-works.cue @@ -0,0 +1,22 @@ +import "time" + +before: time.Parse(time.RFC3339, "2022-04-15T17:10:27Z") +after: time.Parse(time.RFC3339, "2022-03-09T17:10:27Z") + +// The predicateType field must match this string +predicateType: "cosign.sigstore.dev/attestation/vuln/v1" + +predicate: { + invocation: { + uri: "invocation.example.com/cosign-testing" + } + scanner: { + uri: "fakescanner.example.com/cosign-testing" + } + metadata: { + scanStartedOn: after + scanFinishedOn: after + } +} From 8be01a976c21cc23a12945ae7af24a9e17378f64 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 12 Apr 2022 15:22:25 -0700 Subject: [PATCH 2/3] Thanks @hectorj2f for catching a bad upstream rebase. Signed-off-by: Ville Aikas --- cmd/cosign/cli/verify/verify_attestation.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 87747f54d89..fb4cb7713b0 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -18,7 +18,6 @@ package verify import ( "context" "crypto" - "crypto/ecdsa" "flag" "fmt" "os" @@ -121,7 +120,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return errors.Wrap(err, "loading certificate from reference") } if c.CertChain == "" { - co.SigVerifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256) + co.SigVerifier, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256) if err != nil { return errors.Wrap(err, "creating certificate verifier") } From e9d2a25945838696c79b72944f9bda251de481c6 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 12 Apr 2022 15:24:47 -0700 Subject: [PATCH 3/3] Fix typo. Signed-off-by: Ville Aikas --- .github/workflows/kind-verify-attestation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml index d6278c7400e..84b16cf8a0c 100644 --- a/.github/workflows/kind-verify-attestation.yaml +++ b/.github/workflows/kind-verify-attestation.yaml @@ -139,7 +139,7 @@ jobs: fi echo '::endgroup::' - - name: Verify custom attestation with cosign, fails + - name: Verify vuln attestation with cosign, fails run: | echo '::group:: test vuln verify-attestation success' if ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then