Skip to content

Commit

Permalink
Make predicate a required flag in attest commands (#3033)
Browse files Browse the repository at this point in the history
* Make predicate a required flag in attest commands

Fixes #2937

Signed-off-by: Luiz Carvalho <lucarval@redhat.com>

* fix docs format in cosign_attest.md

Signed-off-by: Luiz Carvalho <lucarval@redhat.com>

* Close predicate file during tests

Signed-off-by: Luiz Carvalho <lucarval@redhat.com>

---------

Signed-off-by: Luiz Carvalho <lucarval@redhat.com>
  • Loading branch information
lcarva authored Jun 7, 2023
1 parent 71014a9 commit cd1dbc9
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 17 deletions.
5 changes: 4 additions & 1 deletion cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ func Attest() *cobra.Command {
cosign attest --predicate <FILE> --type <TYPE> --key cosign.key --cert cosign.crt --cert-chain chain.crt <IMAGE>
# attach an attestation to a container image which does not fully support OCI media types
COSIGN_DOCKER_MEDIA_TYPES=1 cosign attest --predicate <FILE> --type <TYPE> --key cosign.key legacy-registry.example.com/my/image`,
COSIGN_DOCKER_MEDIA_TYPES=1 cosign attest --predicate <FILE> --type <TYPE> --key cosign.key legacy-registry.example.com/my/image
# supply attestation via stdin
echo <PAYLOAD> | cosign attest --predicate - <IMAGE>`,

Args: cobra.MinimumNArgs(1),
PersistentPreRun: options.BindViper,
Expand Down
20 changes: 8 additions & 12 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
_ "crypto/sha256" // for `crypto.SHA256`
"encoding/json"
"fmt"
"io"
"os"
"time"

Expand Down Expand Up @@ -90,6 +89,10 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
return &options.KeyParseError{}
}

if c.PredicatePath == "" {
return fmt.Errorf("predicate cannot be empty")
}

predicateURI, err := options.ParsePredicateType(c.PredicateType)
if err != nil {
return err
Expand Down Expand Up @@ -131,18 +134,11 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType)
dd := cremote.NewDupeDetector(sv)

var predicate io.ReadCloser
if c.PredicatePath == "-" {
fmt.Fprintln(os.Stderr, "Using payload from: standard input")
predicate = os.Stdin
} else {
fmt.Fprintln(os.Stderr, "Using payload from:", c.PredicatePath)
predicate, err = os.Open(c.PredicatePath)
if err != nil {
return err
}
defer predicate.Close()
predicate, err := predicateReader(c.PredicatePath)
if err != nil {
return fmt.Errorf("getting predicate reader: %w", err)
}
defer predicate.Close()

sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
Predicate: predicate,
Expand Down
9 changes: 6 additions & 3 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
return &options.KeyParseError{}
}

if c.PredicatePath == "" {
return fmt.Errorf("predicate cannot be empty")
}

if c.Timeout != 0 {
var cancelFn context.CancelFunc
ctx, cancelFn = context.WithTimeout(ctx, c.Timeout)
Expand Down Expand Up @@ -107,10 +111,9 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
hexDigest = c.ArtifactHash
}

fmt.Fprintln(os.Stderr, "Using predicate from:", c.PredicatePath)
predicate, err := os.Open(c.PredicatePath)
predicate, err := predicateReader(c.PredicatePath)
if err != nil {
return err
return fmt.Errorf("getting predicate reader: %w", err)
}
defer predicate.Close()

Expand Down
35 changes: 35 additions & 0 deletions cmd/cosign/cli/attest/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2023 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 attest

import (
"fmt"
"io"
"os"
)

func predicateReader(predicatePath string) (io.ReadCloser, error) {
if predicatePath == "-" {
fmt.Fprintln(os.Stderr, "Using payload from: standard input")
return os.Stdin, nil
}

fmt.Fprintln(os.Stderr, "Using payload from:", predicatePath)
f, err := os.Open(predicatePath)
if err != nil {
return nil, err
}
return f, nil
}
78 changes: 78 additions & 0 deletions cmd/cosign/cli/attest/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2023 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 attest

import (
"os"
"path"
"testing"

"github.com/stretchr/testify/require"
)

func TestPredicateReader(t *testing.T) {
cases := []struct {
name string
path string
wantErr bool
wantStdin bool
createFile bool
}{
{
name: "standard input",
path: "-",
wantStdin: true,
},
{
name: "regular file",
path: "payload.json",
createFile: true,
},
{
name: "missing file",
path: "payload.json",
wantErr: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
pf := tc.path
if tc.createFile {
pf = path.Join(t.TempDir(), tc.path)
err := os.WriteFile(pf, []byte("payload"), 0644)
require.NoError(t, err)
}

got, err := predicateReader(pf)
if err == nil {
defer got.Close()
}

if tc.wantErr {
require.Error(t, err)
return
}

require.NoError(t, err)

if tc.wantStdin {
require.Same(t, os.Stdin, got)
} else {
require.NotSame(t, os.Stdin, got)
}
})
}
}
5 changes: 4 additions & 1 deletion cmd/cosign/cli/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ func AttestBlob() *cobra.Command {
cosign attest-blob --predicate <FILE> --type <TYPE> --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] <BLOB>
# attach an attestation to a blob with a key pair stored in Hashicorp Vault
cosign attest-blob --predicate <FILE> --type <TYPE> --key hashivault://[KEY] <BLOB>`,
cosign attest-blob --predicate <FILE> --type <TYPE> --key hashivault://[KEY] <BLOB>
# supply attestation via stdin
echo <PAYLOAD> | cosign attest-blob --predicate - --yes`,

Args: cobra.ExactArgs(1),
PersistentPreRun: options.BindViper,
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/options/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) {

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

// PredicateRemoteOptions is the wrapper for remote predicate related options.
Expand Down
3 changes: 3 additions & 0 deletions doc/cosign_attest-blob.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions doc/cosign_attest.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cd1dbc9

Please sign in to comment.