From e70b471f2eb213fbb23de0330b59f1173decc05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 20:44:56 -0600 Subject: [PATCH 1/7] Add attestation package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces the attestation package which wraps the openvex attestation module and adds signing and attaching capabilities. Signed-off-by: Adolfo García Veytia (Puerco) --- pkg/attestation/attestation.go | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 pkg/attestation/attestation.go diff --git a/pkg/attestation/attestation.go b/pkg/attestation/attestation.go new file mode 100644 index 0000000..a943440 --- /dev/null +++ b/pkg/attestation/attestation.go @@ -0,0 +1,106 @@ +/* +Copyright 2022 Chainguard, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package attestation + +import ( + "bytes" + "context" + "fmt" + "strings" + "time" + + "github.com/google/go-containerregistry/pkg/crane" + intoto "github.com/in-toto/in-toto-golang/in_toto" + ovattest "github.com/openvex/vex/pkg/attestation" + "github.com/sigstore/cosign/cmd/cosign/cli/options" + "github.com/sigstore/cosign/cmd/cosign/cli/sign" + "github.com/sigstore/sigstore/pkg/signature/dsse" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" +) + +type Attestation struct { + signedData []byte `json:"-"` + ovattest.Attestation +} + +func New() *Attestation { + openVexAttestation := ovattest.New() + return &Attestation{ + Attestation: *openVexAttestation, + } +} + +// Sign the attestation +func (att *Attestation) Sign() error { + ctx := context.Background() + var timeout time.Duration /// TODO move to options + var certPath, certChainPath string + ko := options.KeyOpts{ + // KeyRef: s.options.PrivateKeyPath, + // IDToken: identityToken, + FulcioURL: options.DefaultFulcioURL, + RekorURL: options.DefaultRekorURL, + OIDCIssuer: options.DefaultOIDCIssuerURL, + OIDCClientID: "sigstore", + + InsecureSkipFulcioVerify: false, + SkipConfirmation: true, + // FulcioAuthFlow: "", + } + + if timeout != 0 { + var cancelFn context.CancelFunc + ctx, cancelFn = context.WithTimeout(ctx, timeout) + defer cancelFn() + } + + sv, err := sign.SignerFromKeyOpts(ctx, certPath, certChainPath, ko) + if err != nil { + return fmt.Errorf("getting signer: %w", err) + } + defer sv.Close() + + // Wrap the attestation in the DSSE envelope + wrapped := dsse.WrapSigner(sv, "application/vnd.in-toto+json") + + var b bytes.Buffer + if err := att.ToJSON(&b); err != nil { + return fmt.Errorf("serializing attestation to json: %w", err) + } + + signedPayload, err := wrapped.SignMessage( + bytes.NewReader(b.Bytes()), signatureoptions.WithContext(ctx), + ) + if err != nil { + return fmt.Errorf("signing attestation: %w", err) + } + + att.Signed = true + att.signedData = signedPayload + return nil +} + +func (att *Attestation) AddImageSubjects(imageRefs []string) error { + subs := []intoto.Subject{} + for _, refString := range imageRefs { + digest, err := crane.Digest(refString) + if err != nil { + return fmt.Errorf("getting image digest: %w", err) + } + s := intoto.Subject{ + Name: refString, + Digest: map[string]string{"sha256": strings.TrimPrefix(digest, "sha256:")}, + } + + subs = append(subs, s) + } + + if err := att.AddSubjects(subs); err != nil { + return fmt.Errorf("adding image subjects to attestation: %w", err) + } + + return nil +} From 4637afdeb9ca848ab2f1604f60ff8319d74c0eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 20:48:09 -0600 Subject: [PATCH 2/7] go mod tidy after mod reshuffle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adolfo García Veytia (Puerco) --- go.mod | 6 +++--- go.sum | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 07cf3ec..1a148ac 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.19 require ( github.com/google/go-containerregistry v0.12.1 - github.com/openvex/vex v0.1.1-0.20230110080744-b295df0b0ef1 + github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add + github.com/openvex/vex v0.1.1-0.20230117021350-78649647b8fb github.com/owenrumney/go-sarif v1.1.1 github.com/secure-systems-lab/go-securesystemslib v0.4.0 github.com/sigstore/cosign v1.13.1 + github.com/sigstore/sigstore v1.5.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 sigs.k8s.io/release-utils v0.7.3 @@ -129,7 +131,6 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect github.com/jhump/protoreflect v1.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -168,7 +169,6 @@ require ( github.com/segmentio/ksuid v1.0.4 // indirect github.com/sigstore/fulcio v0.6.0 // indirect github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2 // indirect - github.com/sigstore/sigstore v1.5.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/afero v1.8.2 // indirect diff --git a/go.sum b/go.sum index 88e208f..5c4b00b 100644 --- a/go.sum +++ b/go.sum @@ -1037,8 +1037,8 @@ github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openvex/vex v0.1.1-0.20230110080744-b295df0b0ef1 h1:MlPltqDIi3Q2eshmLhJ7Hhvne4mR2lGnvb7z+rC5Akk= -github.com/openvex/vex v0.1.1-0.20230110080744-b295df0b0ef1/go.mod h1:I3ZjbXZAjc3jM+qCXhA3lu0neNmPEzpH8ZwAjfW4TG0= +github.com/openvex/vex v0.1.1-0.20230117021350-78649647b8fb h1:R0OZhJcNmtIVTRJJfSPcqsWkpOi0A60WMrnX+iNt5gg= +github.com/openvex/vex v0.1.1-0.20230117021350-78649647b8fb/go.mod h1:hyBd9x0no4IVW7VFP+wyVvqwX5mw7Lv5AhlN3owLhdY= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= From 1491ed75c964f1483b1c7c6be076d2da705b3c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 20:48:50 -0600 Subject: [PATCH 3/7] Use local attestation module to sign and attach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attestation signing and attaching was removed in https://github.com/openvex/vex/pull/3 so we now implement that functionality local in vexctl. Signed-off-by: Adolfo García Veytia (Puerco) --- pkg/ctl/ctl.go | 3 ++- pkg/ctl/implementation.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/ctl/ctl.go b/pkg/ctl/ctl.go index 5550c85..3b0d167 100644 --- a/pkg/ctl/ctl.go +++ b/pkg/ctl/ctl.go @@ -9,9 +9,10 @@ import ( "context" "fmt" - "github.com/openvex/vex/pkg/attestation" "github.com/openvex/vex/pkg/sarif" "github.com/openvex/vex/pkg/vex" + + "github.com/openvex/vexctl/pkg/attestation" ) type VexCtl struct { diff --git a/pkg/ctl/implementation.go b/pkg/ctl/implementation.go index 9f4080c..e89e5c7 100644 --- a/pkg/ctl/implementation.go +++ b/pkg/ctl/implementation.go @@ -30,9 +30,9 @@ import ( "github.com/sirupsen/logrus" "sigs.k8s.io/release-utils/util" - "github.com/openvex/vex/pkg/attestation" "github.com/openvex/vex/pkg/sarif" "github.com/openvex/vex/pkg/vex" + "github.com/openvex/vexctl/pkg/attestation" ) const IntotoPayloadType = "application/vnd.in-toto+json" From d17ff83c266c2decd7902041e975c8285255b647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 21:48:11 -0600 Subject: [PATCH 4/7] Improve attest subcommand help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adolfo García Veytia (Puerco) --- internal/cmd/attest.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/internal/cmd/attest.go b/internal/cmd/attest.go index 4a3a0e1..4a3483f 100644 --- a/internal/cmd/attest.go +++ b/internal/cmd/attest.go @@ -23,8 +23,31 @@ type attestOptions struct { func addAttest(parentCmd *cobra.Command) { opts := attestOptions{} generateCmd := &cobra.Command{ - Short: fmt.Sprintf("%s attest: generate a VEX attestation", appname), - Long: ``, + Short: fmt.Sprintf("%s attest: generate a VEX attestation", appname), + Long: fmt.Sprintf(`%s attest: generate an VEX attestation +The attach subcommand lets users wrap OpenVEX documents in in-toto attestations. +Attestations generated by %s can be signed with sigstore and attached to container +images stored in an OCI registry. + +In its simplest form, %s will create an attestation from an OpenVEX file and +write it to stdout: + + %s attest data.vex.json + +If the vex document defines any subjects, %s will read the product entries from +the document and transfer them to the attestation's subjects section (see the +in-toto documentation for more info). + +Passing the --sign flag will trigger the cosign signing flow, either asking for +credentials from the user or trying to get them from the environment: + + %s attest --sign data.vex.json + +Further positional arguments are considered to be container images and will be +added to the attestation as subjects + + +`, appname, appname, appname, appname, appname, appname), Use: "attest", SilenceUsage: false, SilenceErrors: false, @@ -62,14 +85,14 @@ func addAttest(parentCmd *cobra.Command) { generateCmd.PersistentFlags().BoolVar( &opts.attach, "attach", - true, + false, "attach the generated attestation to an image", ) generateCmd.PersistentFlags().BoolVar( &opts.sign, "sign", - true, + false, "sign the attestation with sigstore", ) From 84a30cfce4d7466460dfd1f8979462c4b628370c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 21:49:33 -0600 Subject: [PATCH 5/7] Intercept vex/attestation ToJSON method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This comit adds a ToJSON method to the vexctl attestation that intercepts the call to the /vex equivalent to output any signed data instead of the plain attestation. Signed-off-by: Adolfo García Veytia (Puerco) --- pkg/attestation/attestation.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pkg/attestation/attestation.go b/pkg/attestation/attestation.go index a943440..8f3e633 100644 --- a/pkg/attestation/attestation.go +++ b/pkg/attestation/attestation.go @@ -8,7 +8,9 @@ package attestation import ( "bytes" "context" + "errors" "fmt" + "io" "strings" "time" @@ -24,6 +26,7 @@ import ( type Attestation struct { signedData []byte `json:"-"` ovattest.Attestation + Signed bool `json:"-"` } func New() *Attestation { @@ -104,3 +107,19 @@ func (att *Attestation) AddImageSubjects(imageRefs []string) error { return nil } + +// ToJSON intercepts the openves to json call and if the attestation is signed +// writes the signed data to io.Writer w instead of the original attestation. +func (att *Attestation) ToJSON(w io.Writer) error { + if !att.Signed { + return att.Attestation.ToJSON(w) + } + if len(att.signedData) == 0 { + return errors.New("consistency error: attestation is signed but data is empty") + } + + if _, err := w.Write(att.signedData); err != nil { + return fmt.Errorf("writing signed attestation: %w", err) + } + return nil +} From e2867d833a7bf76869320a6a1713915fbd986f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 21:51:03 -0600 Subject: [PATCH 6/7] Move signing call to Attest() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now sign when generating the attestation, this allows us to get the signed json without attaching. Signed-off-by: Adolfo García Veytia (Puerco) --- pkg/ctl/ctl.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/ctl/ctl.go b/pkg/ctl/ctl.go index 3b0d167..85f7a68 100644 --- a/pkg/ctl/ctl.go +++ b/pkg/ctl/ctl.go @@ -72,18 +72,18 @@ func (vexctl *VexCtl) Attest(vexDataPath string, imageRefs []string) (*attestati return nil, fmt.Errorf("adding image references to attestation") } - return att, nil -} - -// Attach attaches an attestation to a list of images -func (vexctl *VexCtl) Attach(ctx context.Context, att *attestation.Attestation, imageRefs []string) (err error) { // Sign the attestation if vexctl.Options.Sign { if err := att.Sign(); err != nil { - return fmt.Errorf("signing attestation: %w", err) + return att, fmt.Errorf("signing attestation: %w", err) } } + return att, nil +} + +// Attach attaches an attestation to a list of images +func (vexctl *VexCtl) Attach(ctx context.Context, att *attestation.Attestation, imageRefs []string) (err error) { for _, ref := range imageRefs { if err := vexctl.impl.Attach(ctx, att, ref); err != nil { return fmt.Errorf("attaching attestation: %w", err) From 50bb97d3d1a6648a39b8d780d9e80a79d5d370ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 16 Jan 2023 21:52:10 -0600 Subject: [PATCH 7/7] attach: Support shorthand flags for sign and attach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since signing and attaching now defaults to false, we add shorthand flags as they will be typed a lot more. Signed-off-by: Adolfo García Veytia (Puerco) --- internal/cmd/attest.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/cmd/attest.go b/internal/cmd/attest.go index 4a3483f..85925e5 100644 --- a/internal/cmd/attest.go +++ b/internal/cmd/attest.go @@ -82,16 +82,18 @@ added to the attestation as subjects }, } - generateCmd.PersistentFlags().BoolVar( + generateCmd.PersistentFlags().BoolVarP( &opts.attach, "attach", + "a", false, "attach the generated attestation to an image", ) - generateCmd.PersistentFlags().BoolVar( + generateCmd.PersistentFlags().BoolVarP( &opts.sign, "sign", + "s", false, "sign the attestation with sigstore", )