Skip to content

Commit

Permalink
Add option to verify attestations from local image (#1174)
Browse files Browse the repository at this point in the history
This is very similar to a previous commit that added
support for verifying signatures from a local image.

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper authored Dec 9, 2021
1 parent d0d91ab commit 5a5914f
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 12 deletions.
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

0 comments on commit 5a5914f

Please sign in to comment.