Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to verify attestations from local image #1174

Merged
merged 1 commit into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
"signature content or path or remote URL")

cmd.Flags().BoolVar(&o.LocalImage, "local-image", false,
"whether the path to the image is a local directory")
"whether the specified image is a path to an image saved locally via 'cosign save'")
}

// VerifyAttestationOptions is the top level wrapper for the `verify attestation` command.
Expand All @@ -85,6 +85,7 @@ type VerifyAttestationOptions struct {
Registry RegistryOptions
Predicate PredicateRemoteOptions
Policies []string
LocalImage bool
}

var _ Interface = (*VerifyAttestationOptions)(nil)
Expand All @@ -108,6 +109,9 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVarP(&o.Output, "output", "o", "json",
"output format for the signing image information (json|text)")

cmd.Flags().BoolVar(&o.LocalImage, "local-image", false,
"whether the specified image is a path to an image saved locally via 'cosign save'")
}

// VerifyBlobOptions is the top level wrapper for the `verify blob` command.
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ against the transparency log.`,
# verify image with public key
cosign verify-attestation --key cosign.pub <IMAGE>

# verify image attestations with an on-disk signed image from 'cosign save'
cosign verify-attestation --key cosign.pub --local-image <PATH>

# verify image with public key provided by URL
cosign verify-attestation --key https://host.for/<FILE> <IMAGE>

Expand Down Expand Up @@ -164,6 +167,7 @@ against the transparency log.`,
FulcioURL: o.Fulcio.URL,
PredicateType: o.Predicate.Type,
Policies: o.Policies,
LocalImage: o.LocalImage,
}
return v.Exec(cmd.Context(), args)
},
Expand Down
26 changes: 19 additions & 7 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/pkg/errors"
"github.com/sigstore/cosign/pkg/cosign/pkcs11key"
"github.com/sigstore/cosign/pkg/cosign/rego"
"github.com/sigstore/cosign/pkg/oci"

"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
Expand All @@ -52,6 +53,7 @@ type VerifyAttestationCommand struct {
RekorURL string
PredicateType string
Policies []string
LocalImage bool
}

// Exec runs the verification command
Expand Down Expand Up @@ -109,14 +111,24 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
}

for _, imageRef := range images {
ref, err := name.ParseReference(imageRef)
if err != nil {
return err
}
var verified []oci.Signature
var bundleVerified bool

verified, bundleVerified, err := cosign.VerifyImageAttestations(ctx, ref, co)
if err != nil {
return err
if c.LocalImage {
verified, bundleVerified, err = cosign.VerifyLocalImageAttestations(ctx, imageRef, co)
if err != nil {
return err
}
} else {
ref, err := name.ParseReference(imageRef)
if err != nil {
return err
}

verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co)
if err != nil {
return err
}
}

var cuePolicies, regoPolicies []string
Expand Down
2 changes: 1 addition & 1 deletion doc/cosign_dockerfile_verify.md

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

2 changes: 1 addition & 1 deletion doc/cosign_manifest_verify.md

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

4 changes: 4 additions & 0 deletions doc/cosign_verify-attestation.md

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

2 changes: 1 addition & 1 deletion doc/cosign_verify.md

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

51 changes: 51 additions & 0 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,57 @@ func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, c
if err != nil {
return nil, false, err
}

return verifyImageAttestations(ctx, atts, h, co)
}

// VerifyLocalImageAttestations verifies attestations from a saved, local image, without any network calls,
// returning the verified attestations.
// If there were no valid signatures, we return an error.
func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
// Enforce this up front.
if co.RootCerts == nil && co.SigVerifier == nil {
return nil, false, errors.New("one of verifier or root certs is required")
}

se, err := layout.SignedImageIndex(path)
if err != nil {
return nil, false, err
}

var h v1.Hash
// Verify either an image index or image.
ii, err := se.SignedImageIndex(v1.Hash{})
if err != nil {
return nil, false, err
}
i, err := se.SignedImage(v1.Hash{})
if err != nil {
return nil, false, err
}
switch {
case ii != nil:
h, err = ii.Digest()
if err != nil {
return nil, false, err
}
case i != nil:
h, err = i.Digest()
if err != nil {
return nil, false, err
}
default:
return nil, false, errors.New("must verify either an image index or image")
}

atts, err := se.Attestations()
if err != nil {
return nil, false, err
}
return verifyImageAttestations(ctx, atts, h, co)
}

func verifyImageAttestations(ctx context.Context, atts oci.Signatures, h v1.Hash, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
sl, err := atts.Get()
if err != nil {
return nil, false, err
Expand Down
5 changes: 4 additions & 1 deletion test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,12 +720,15 @@ func TestSaveLoadAttestation(t *testing.T) {
}
verifyAttestation.PredicateType = "slsaprovenance"
verifyAttestation.Policies = []string{policyPath}
// Success case
// Success case (remote)
cuePolicy := `builder: id: "2"`
if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
t.Fatal(err)
}
must(verifyAttestation.Exec(ctx, []string{imgName2}), t)
// Success case (local)
verifyAttestation.LocalImage = true
must(verifyAttestation.Exec(ctx, []string{imageDir}), t)
}

func TestAttachSBOM(t *testing.T) {
Expand Down