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

feat: use new verify workflow #373

Merged
merged 6 commits into from
Oct 18, 2022
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
172 changes: 56 additions & 116 deletions cmd/notation/verify.go
Original file line number Diff line number Diff line change
@@ -1,53 +1,37 @@
package main

import (
"context"
"errors"
"fmt"
"os"
"strings"

"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/dir"
"github.com/notaryproject/notation-go/signature"
notationregistry "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation-go/verification"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/envelope"
"github.com/notaryproject/notation/internal/slices"
"github.com/notaryproject/notation/pkg/cache"
"github.com/notaryproject/notation/pkg/configutil"
"github.com/opencontainers/go-digest"
"github.com/notaryproject/notation/internal/ioutil"

"github.com/spf13/cobra"
"oras.land/oras-go/v2/registry"
)

type verifyOpts struct {
RemoteFlagOpts
signatures []string
certs []string
certFiles []string
pull bool
reference string
SecureFlagOpts
reference string
pluginConfig string
}

func verifyCommand(opts *verifyOpts) *cobra.Command {
if opts == nil {
opts = &verifyOpts{}
}
command := &cobra.Command{
Use: "verify [reference]",
Short: "Verify OCI artifacts",
Long: `Verify OCI artifacts
Use: "verify [flags] <reference>",
Short: "Verifies OCI Artifacts",
Long: `Verifies OCI Artifacts

Prerequisite: a trusted certificate needs to be generated or added using the command "notation cert".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to delete it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep the Prerequisite comment.


Example - Verify a signature using the trusted certificate:
notation verify <registry>/<repository>:<tag>

Example - Verify a signature associated with an OCI artifact identified by the digest:
notation verify <registry>/<repository>@<digest>

Example - Verify a signature using a trusted certificate in a specified path:
notation verify --cert-file <cert_path> <registry>/<repository>:<tag>
`,
notation verify [--plugin-config <key>=<value>,...] [--username <username>] [--password <password>] <reference>`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing reference")
Expand All @@ -59,119 +43,75 @@ Example - Verify a signature using a trusted certificate in a specified path:
return runVerify(cmd, opts)
},
}
setFlagSignature(command.Flags(), &opts.signatures)
command.Flags().StringSliceVarP(&opts.certs, "cert", "c", []string{}, "certificate names for verification")
command.Flags().StringSliceVar(&opts.certFiles, cmd.PflagCertFile.Name, []string{}, "certificate files for verification")
command.Flags().BoolVar(&opts.pull, "pull", true, "pull remote signatures before verification")
opts.ApplyFlags(command.Flags())
command.Flags().StringVarP(&opts.pluginConfig, "plugin-config", "c", "", "list of comma-separated {key}={value} pairs that are passed as is to the plugin")
return command
}

func runVerify(command *cobra.Command, opts *verifyOpts) error {
// initialize
verifier, err := getVerifier(opts)
// resolve the given reference and set the digest.
ref, err := resolveReference(command, opts)
if err != nil {
return err
}
manifestDesc, err := getManifestDescriptorFromContext(command.Context(), &opts.RemoteFlagOpts, opts.reference)

// initialize verifier.
verifier, err := getVerifier(opts, ref)
if err != nil {
return err
}

sigPaths := opts.signatures
if len(sigPaths) == 0 {
if !opts.Local && opts.pull {
if err := pullSignatures(command, opts.reference, &opts.SecureFlagOpts, digest.Digest(manifestDesc.Digest)); err != nil {
return err
}
}
manifestDigest := digest.Digest(manifestDesc.Digest)
sigDigests, err := cache.SignatureDigests(manifestDigest)
if err != nil {
return err
}
for _, sigDigest := range sigDigests {
sigPaths = append(sigPaths, dir.Path.CachedSignature(manifestDigest, sigDigest))
}
}

// core process
if err := verifySignatures(command.Context(), verifier, manifestDesc, sigPaths); err != nil {
// set up verification plugin config.
configs, err := cmd.ParseFlagPluginConfig(opts.pluginConfig)
if err != nil {
return err
}

// write out
fmt.Println(manifestDesc.Digest)
return nil
// core verify process.
ctx := verification.WithPluginConfig(command.Context(), configs)
outcomes, err := verifier.Verify(ctx, ref.String())

// write out.
return ioutil.PrintVerificationResults(os.Stdout, outcomes, err, ref.Reference)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should return the error from verifier.Verify() here so that CLI can throw a non-zero error code when signature verification fails.

}

func verifySignatures(ctx context.Context, verifier notation.Verifier, manifestDesc notation.Descriptor, sigPaths []string) error {
if len(sigPaths) == 0 {
return errors.New("verification failure: no signatures found")
func getVerifier(opts *verifyOpts, ref registry.Reference) (*verification.Verifier, error) {
authClient, plainHTTP, err := getAuthClient(&opts.SecureFlagOpts, ref)
if err != nil {
return nil, err
}

var lastErr error
for _, path := range sigPaths {
sig, err := os.ReadFile(path)
if err != nil {
lastErr = fmt.Errorf("verification failure: %v", err)
continue
}
// pass in nonempty annotations if needed
sigMediaType, err := envelope.SpeculateSignatureEnvelopeFormat(sig)
if err != nil {
lastErr = fmt.Errorf("verification failure: %v", err)
continue
}
opts := notation.VerifyOptions{
SignatureMediaType: sigMediaType,
}
desc, err := verifier.Verify(ctx, sig, opts)
if err != nil {
lastErr = fmt.Errorf("verification failure: %v", err)
continue
}

if !desc.Equal(manifestDesc) {
lastErr = fmt.Errorf("verification failure: %s", manifestDesc.Digest)
continue
}
return nil
}
return lastErr
repo := notationregistry.NewRepositoryClient(authClient, ref, plainHTTP)

return verification.NewVerifier(repo)
}

func getVerifier(opts *verifyOpts) (notation.Verifier, error) {
certPaths, err := appendCertPathFromName(opts.certFiles, opts.certs)
func resolveReference(command *cobra.Command, opts *verifyOpts) (registry.Reference, error) {
ref, err := registry.ParseReference(opts.reference)
if err != nil {
return nil, err
return registry.Reference{}, err
}
if len(certPaths) == 0 {
cfg, err := configutil.LoadConfigOnce()
if err != nil {
return nil, err
}
if len(cfg.VerificationCertificates.Certificates) == 0 {
return nil, errors.New("trust certificate not specified")
}
for _, ref := range cfg.VerificationCertificates.Certificates {
certPaths = append(certPaths, ref.Path)
}

if isDigestReference(opts.reference) {
return ref, nil
}
return signature.NewVerifierFromFiles(certPaths)

// Resolve tag reference to digest reference.
manifestDesc, err := getManifestDescriptorFromReference(command.Context(), &opts.SecureFlagOpts, opts.reference)
if err != nil {
return registry.Reference{}, err
}

ref.Reference = manifestDesc.Digest.String()
return ref, nil
}

func appendCertPathFromName(paths, names []string) ([]string, error) {
for _, name := range names {
cfg, err := configutil.LoadConfigOnce()
if err != nil {
return nil, err
}
idx := slices.Index(cfg.VerificationCertificates.Certificates, name)
if idx < 0 {
return nil, errors.New("verification certificate not found: " + name)
}
paths = append(paths, cfg.VerificationCertificates.Certificates[idx].Path)
func isDigestReference(reference string) bool {
parts := strings.SplitN(reference, "/", 2)
if len(parts) == 1 {
return false
}
return paths, nil

index := strings.Index(parts[1], "@")
return index != -1
}
40 changes: 8 additions & 32 deletions cmd/notation/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,15 @@ func TestVerifyCommand_BasicArgs(t *testing.T) {
command := verifyCommand(opts)
expected := &verifyOpts{
reference: "ref",
RemoteFlagOpts: RemoteFlagOpts{
SecureFlagOpts: SecureFlagOpts{
Username: "user",
Password: "password",
},
CommonFlagOpts: CommonFlagOpts{
MediaType: defaultMediaType,
},
SecureFlagOpts: SecureFlagOpts{
Username: "user",
Password: "password",
},
certs: []string{"cert0", "cert1"},
certFiles: []string{"certfile0", "certfile1"},
signatures: []string{"sig0", "sig1"},
pull: true,
}
if err := command.ParseFlags([]string{
expected.reference,
"--username", expected.Username,
"--password", expected.Password,
"-c", expected.certs[0],
"--cert", expected.certs[1],
"--cert-file", expected.certFiles[0],
"--cert-file", expected.certFiles[1],
"--signature", expected.signatures[0],
"-s", expected.signatures[1]}); err != nil {
"--password", expected.Password}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
Expand All @@ -49,24 +34,15 @@ func TestVerifyCommand_MoreArgs(t *testing.T) {
command := verifyCommand(opts)
expected := &verifyOpts{
reference: "ref",
RemoteFlagOpts: RemoteFlagOpts{
SecureFlagOpts: SecureFlagOpts{
PlainHTTP: true,
},
CommonFlagOpts: CommonFlagOpts{
MediaType: "mediaT",
},
SecureFlagOpts: SecureFlagOpts{
PlainHTTP: true,
},
certs: []string{},
certFiles: []string{},
signatures: []string{},
pull: false,
pluginConfig: "key1=val1,key2=val2",
}
if err := command.ParseFlags([]string{
expected.reference,
"--plain-http",
"--pull=false",
"--media-type=mediaT"}); err != nil {
"--plugin-config", expected.pluginConfig}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ require (
)

require (
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-ldap/ldap/v3 v3.4.4 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/oras-project/artifacts-spec v1.0.0-rc.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
)
24 changes: 24 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f h1:3NCYdjXycNd/Xn/iICZzmxkiDX1e1cjTHjbMAz+wRVk=
github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs=
github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
Expand All @@ -21,20 +29,36 @@ github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/oras-project/artifacts-spec v1.0.0-rc.2 h1:9SMCNSxkJEHqWGDiMCuy6TXHgvjgwXGdXZZGXLKQvVE=
github.com/oras-project/artifacts-spec v1.0.0-rc.2/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83 h1:g8vDfnNOPcGzg6mnlBGc0J5t5lAJkaepXqbc9qFRnFs=
github.com/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
oras.land/oras-go/v2 v2.0.0-rc.3 h1:O4GeIwJ9Ge7rbCkqa/M7DLrL55ww+ZEc+Rhc63OYitU=
oras.land/oras-go/v2 v2.0.0-rc.3/go.mod h1:PrY+cCglzK/DrQoJUtxbYVbL94ZHecVS3eJR01RglpE=
Loading