diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 2eef8b90e88..45607cf0eb8 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -22,6 +22,7 @@ import ( "encoding/pem" "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -106,6 +107,21 @@ func GetAttachedImageRef(ref name.Reference, attachment string, opts ...ociremot return nil, fmt.Errorf("unknown attachment type %s", attachment) } +// ParseOCIReference parses a string reference to an OCI image into a reference, warning if the reference did not include a digest. +func ParseOCIReference(refStr string, out io.Writer) (name.Reference, error) { + ref, err := name.ParseReference(refStr) + if err != nil { + return nil, fmt.Errorf("parsing reference: %w", err) + } + if _, ok := ref.(name.Digest); !ok { + msg := fmt.Sprintf(TagReferenceMessage, refStr) + if _, err := io.WriteString(out, msg); err != nil { + panic("cannot write") + } + } + return ref, nil +} + // nolint func SignCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.RegistryOptions, annotations map[string]interface{}, imgs []string, certPath string, certChainPath string, upload bool, outputSignature, outputCertificate string, @@ -145,24 +161,20 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.Regist ErrDone = mutate.ErrSkipChildren } + opts, err := regOpts.ClientOpts(ctx) + if err != nil { + return fmt.Errorf("constructing client options: %w", err) + } for _, inputImg := range imgs { - ref, err := name.ParseReference(inputImg) - if err != nil { - return fmt.Errorf("parsing reference: %w", err) - } - opts, err := regOpts.ClientOpts(ctx) + ref, err := ParseOCIReference(inputImg, os.Stderr) if err != nil { - return fmt.Errorf("constructing client options: %w", err) + return err } ref, err = GetAttachedImageRef(ref, attachment, opts...) if err != nil { return fmt.Errorf("unable to resolve attachment %s for image %s", attachment, inputImg) } - if _, ok := ref.(name.Tag); ok { - fmt.Fprintf(os.Stderr, TagReferenceMessage, inputImg) - } - if digest, ok := ref.(name.Digest); ok && !recursive { se, err := ociremote.SignedEntity(ref, opts...) if err != nil { diff --git a/cmd/cosign/cli/sign/sign_test.go b/cmd/cosign/cli/sign/sign_test.go index 5b5266623b8..c8631ac08c8 100644 --- a/cmd/cosign/cli/sign/sign_test.go +++ b/cmd/cosign/cli/sign/sign_test.go @@ -204,3 +204,28 @@ func Test_signerFromKeyRefFailureEmptyChainFile(t *testing.T) { t.Fatalf("expected empty chain error, got %v", err) } } + +func Test_ParseOCIReference(t *testing.T) { + var tests = []struct { + ref string + expected string + }{ + {"image:bytag", "WARNING: Image reference image:bytag uses a tag, not a digest"}, + {"image:bytag@sha256:abcdef", ""}, + {"image:@sha256:abcdef", ""}, + } + for _, tt := range tests { + var buf strings.Builder + ParseOCIReference(tt.ref, &buf) + actual := buf.String() + if len(tt.expected) == 0 { + if len(actual) != 0 { + t.Errorf("expected no warning, got %s", actual) + } + } else { + if strings.Contains(tt.expected, actual) { + t.Errorf("bad warning: expected match for `%s`, got %s", tt.expected, actual) + } + } + } +}