Skip to content

Commit

Permalink
feat: add validation for predicates via cue or rego policy files support
Browse files Browse the repository at this point in the history
Signed-off-by: Batuhan Apaydın <[email protected]>
Co-authored-by: Erkan Zileli <[email protected]>
Co-authored-by: Scott Nichols <[email protected]>
Co-authored-by: Furkan Türkal <[email protected]>
Co-authored-by: Dan Lorenc <[email protected]>
  • Loading branch information
5 people committed Oct 10, 2021
1 parent 278ad7d commit 1a3ba99
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 44 deletions.
27 changes: 4 additions & 23 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"os"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/pkg/errors"

"github.com/sigstore/cosign/cmd/cosign/cli/options"
Expand All @@ -45,20 +43,6 @@ import (
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
)

const (
predicateCustom = "custom"
predicateSlsa = "slsaprovenance"
predicateSpdx = "spdx"
predicateLink = "link"
)

var predicateTypeMap = map[string]string{
predicateCustom: attestation.CosignCustomProvenanceV01,
predicateSlsa: in_toto.PredicateSLSAProvenanceV01,
predicateSpdx: in_toto.PredicateSPDX,
predicateLink: in_toto.PredicateLinkV1,
}

//nolint
func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOptions, imageRef string, certPath string,
upload bool, predicatePath string, force bool, predicateType string) error {
Expand All @@ -73,19 +57,16 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt
}
}

predicateURI, ok := predicateTypeMap[predicateType]
if !ok {
if _, err := url.ParseRequestURI(predicateType); err != nil {
return fmt.Errorf("invalid predicate type: %s", predicateType)
} else {
predicateURI = predicateType
}
predicateURI, err := options.ParsePredicateType(predicateType)
if err != nil {
return err
}

ref, err := name.ParseReference(imageRef)
if err != nil {
return errors.Wrap(err, "parsing reference")
}

ociremoteOpts, err := regOpts.ClientOpts(ctx)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/options/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type AttestOptions struct {
Rekor RekorOptions
Fulcio FulcioOptions
SecurityKey SecurityKeyOptions
Predicate PredicateOptions
Predicate PredicateLocalOptions
Registry RegistryOptions
}

Expand Down
63 changes: 60 additions & 3 deletions cmd/cosign/cli/options/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,79 @@
package options

import (
"fmt"
"net/url"

"github.com/in-toto/in-toto-golang/in_toto"
"github.com/spf13/cobra"

"github.com/sigstore/cosign/pkg/cosign/attestation"
)

const (
PredicateCustom = "custom"
PredicateSLSA = "slsaprovenance"
PredicateSPDX = "spdx"
PredicateLink = "link"
)

// PredicateTypeMap is the mapping between the predicate `type` option to predicate URI.
var PredicateTypeMap = map[string]string{
PredicateCustom: attestation.CosignCustomProvenanceV01,
PredicateSLSA: in_toto.PredicateSLSAProvenanceV01,
PredicateSPDX: in_toto.PredicateSPDX,
PredicateLink: in_toto.PredicateLinkV1,
}

// PredicateOptions is the wrapper for predicate related options.
type PredicateOptions struct {
Path string
Type string
}

var _ Interface = (*PredicateOptions)(nil)

// AddFlags implements Interface
func (o *PredicateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Type, "type", "custom",
"specify a predicate type (slsaprovenance|link|spdx|custom) or an URI")
}

// ParsePredicateType parses the predicate `type` flag passed into a predicate URI, or validates `type` is a valid URI.
func ParsePredicateType(t string) (string, error) {
uri, ok := PredicateTypeMap[t]
if !ok {
if _, err := url.ParseRequestURI(t); err != nil {
return "", fmt.Errorf("invalid predicate type: %s", t)
}
uri = t
}
return uri, nil
}

// PredicateLocalOptions is the wrapper for predicate related options.
type PredicateLocalOptions struct {
PredicateOptions
Path string
}

var _ Interface = (*PredicateLocalOptions)(nil)

// AddFlags implements Interface
func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) {
o.PredicateOptions.AddFlags(cmd)

cmd.Flags().StringVar(&o.Path, "predicate", "",
"path to the predicate file.")
}

cmd.Flags().StringVar(&o.Type, "type", "custom",
"specify a predicate type (slsaprovenance|link|spdx|custom) or an URI")
// PredicateRemoteOptions is the wrapper for remote predicate related options.
type PredicateRemoteOptions struct {
PredicateOptions
}

var _ Interface = (*PredicateRemoteOptions)(nil)

// AddFlags implements Interface
func (o *PredicateRemoteOptions) AddFlags(cmd *cobra.Command) {
o.PredicateOptions.AddFlags(cmd)
}
6 changes: 6 additions & 0 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type VerifyAttestationOptions struct {
Rekor RekorOptions
Fulcio FulcioOptions // TODO: the original command did not use id token, mistake?
Registry RegistryOptions
Predicate PredicateRemoteOptions
Policies []string
}

var _ Interface = (*VerifyAttestationOptions)(nil)
Expand All @@ -79,13 +81,17 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {
o.Rekor.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.Predicate.AddFlags(cmd)

cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")

cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"whether to check the claims found")

cmd.Flags().StringSliceVar(&o.Policies, "policy", nil,
"specify CUE or Rego files will be using for validation")

cmd.Flags().StringVarP(&o.Output, "output", "o", "json",
"output format for the signing image information (json|text)")
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ against the transparency log.`,
Output: o.Output,
RekorURL: o.Rekor.URL,
FulcioURL: o.Fulcio.URL,
PredicateType: o.Predicate.Type,
Policies: o.Policies,
}
return v.Exec(cmd.Context(), args)
},
Expand Down
143 changes: 136 additions & 7 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,23 @@ package verify

import (
"context"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"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/rego"

"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/cue"
"github.com/sigstore/cosign/pkg/cosign/pivkey"
sigs "github.com/sigstore/cosign/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature"
Expand All @@ -36,13 +44,15 @@ import (
// nolint
type VerifyAttestationCommand struct {
options.RegistryOptions
CheckClaims bool
KeyRef string
Sk bool
Slot string
Output string
FulcioURL string
RekorURL string
CheckClaims bool
KeyRef string
Sk bool
Slot string
Output string
FulcioURL string
RekorURL string
PredicateType string
Policies []string
}

// DSSE messages contain the signature and payload in one object, but our interface expects a signature and payload
Expand Down Expand Up @@ -117,6 +127,125 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
return err
}

var cuePolicies, regoPolicies []string

fmt.Println(c.Policies)

for _, policy := range c.Policies {
switch filepath.Ext(policy) {
case ".rego":
regoPolicies = append(regoPolicies, policy)
case ".cue":
cuePolicies = append(cuePolicies, policy)
default:
return errors.New("invalid policy format, expected .cue or .rego")
}
}

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)
if err != nil {
return errors.Wrap(err, "unmarshal payload data")
}

predicateURI, ok := options.PredicateTypeMap[c.PredicateType]
if !ok {
return fmt.Errorf("invalid predicate type: %s", c.PredicateType)
}

// sanity checks
if val, ok := payloadData["payloadType"]; ok {
// we need to check only given type from the cli flag
// so we are skipping other types
if predicateURI != val {
continue
}
} else {
return fmt.Errorf("could not find 'payloadType' in payload data")
}

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")
}

var payload []byte
switch c.PredicateType {
case options.PredicateCustom:
var cosignStatement in_toto.Statement
if err := json.Unmarshal(decodedPayload, &cosignStatement); err != nil {
return fmt.Errorf("unmarshal CosignStatement: %w", err)
}
payload, err = json.Marshal(cosignStatement.Predicate)
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.Predicate)
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.Predicate)
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.Predicate)
if err != nil {
return fmt.Errorf("error when generating SPDXStatement: %w", err)
}
}

if len(cuePolicies) > 0 {
fmt.Fprintf(os.Stderr, "will be validating against CUE policies: %v\n", cuePolicies)
if err := cue.ValidateJSON(payload, cuePolicies); err != nil {
validationErrors = append(validationErrors, err)
}
}

if len(regoPolicies) > 0 {
fmt.Fprintf(os.Stderr, "will be validating against Rego policies: %v\n", regoPolicies)
if err := rego.ValidateJSON(payload, regoPolicies); err != nil {
validationErrors = append(validationErrors, err)
}
}
}

if len(validationErrors) > 0 {
fmt.Fprintf(os.Stderr, "There are %d number of errors occurred during the validation:\n", len(validationErrors))
for _, v := range validationErrors {
_, _ = fmt.Fprintf(os.Stderr, "- %v\n", v)
}
return fmt.Errorf("%d validation errors occurred", len(validationErrors))
}

// TODO: add CUE validation report to `PrintVerificationHeader`.
PrintVerificationHeader(imageRef, co, bundleVerified)
// The attestations are always JSON, so use the raw "text" mode for outputting them instead of conversion
PrintVerification(imageRef, verified, "text")
Expand Down
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,16 @@ require (

require (
cloud.google.com/go/kms v1.0.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
cuelang.org/go v0.4.0
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.1 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211004163346-9ae11fe20941
github.com/imdario/mergo v0.3.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/onsi/gomega v1.16.0 // indirect
github.com/open-policy-agent/opa v0.33.1
github.com/prometheus/procfs v0.7.3 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.1.0
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613
Expand Down
Loading

0 comments on commit 1a3ba99

Please sign in to comment.