Skip to content

Commit

Permalink
feat: add support for signed user metadata in notation sign and verif…
Browse files Browse the repository at this point in the history
…y cmds (notaryproject#507)

Adds support for signed user metadata in `notation sign` and `notation verify`. [Relevant spec](notaryproject#498)

example sign usage:
chienb@a07817b52895 notation % notation sign $IMAGE --user-metadata io.wabbit-networks.buildId=123 --user-metadata io.wabbit-networks.buildTime=123
Successfully signed localhost:5000/net-monitor@sha256:5a07385af4e6b6af81b0ebfd435aedccdfa3507f0609c658209e1aba57159b2b
---------------
example verification:
chienb@a07817b52895 notation % notation verify $IMAGE --user-metadata io.wabbit-networks.buildTime=123
Resolved artifact tag `v1` to digest `sha256:5a07385af4e6b6af81b0ebfd435aedccdfa3507f0609c658209e1aba57159b2b` before verification.
Warning: The resolved digest may not point to the same signed artifact, since tags are mutable.
Successfully verified signature for localhost:5000/net-monitor@sha256:5a07385af4e6b6af81b0ebfd435aedccdfa3507f0609c658209e1aba57159b2b

The artifact was signed with the following user metadata.
KEY                            VALUE
io.wabbit-networks.buildTime   123
io.wabbit-networks.buildId     123
-----

Signed-off-by: Byron Chien <[email protected]>
  • Loading branch information
byronchien authored Feb 8, 2023
1 parent 8d52989 commit 51951af
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 22 deletions.
2 changes: 1 addition & 1 deletion cmd/notation/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func addKey(ctx context.Context, opts *keyAddOpts) error {
// set log level
ctx = opts.LoggingFlagOpts.SetLoggerLevel(ctx)

pluginConfig, err := cmd.ParseFlagPluginConfig(opts.pluginConfig)
pluginConfig, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name)
if err != nil {
return err
}
Expand Down
30 changes: 19 additions & 11 deletions cmd/notation/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type signOpts struct {
SecureFlagOpts
expiry time.Duration
pluginConfig []string
userMetadata []string
reference string
}

Expand Down Expand Up @@ -67,6 +68,7 @@ Example - Sign an OCI artifact stored in a registry and specify the signature ex
opts.SecureFlagOpts.ApplyFlags(command.Flags())
cmd.SetPflagExpiry(command.Flags(), &opts.expiry)
cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig)
cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataSignUsage)
return command
}

Expand Down Expand Up @@ -99,29 +101,35 @@ func runSign(command *cobra.Command, cmdOpts *signOpts) error {
return nil
}

func prepareSigningContent(ctx context.Context, opts *signOpts, sigRepo notationregistry.Repository) (notation.SignOptions, registry.Reference, error) {
func prepareSigningContent(ctx context.Context, opts *signOpts, sigRepo notationregistry.Repository) (notation.RemoteSignOptions, registry.Reference, error) {
ref, err := resolveReference(ctx, &opts.SecureFlagOpts, opts.reference, sigRepo, func(ref registry.Reference, manifestDesc ocispec.Descriptor) {
fmt.Fprintf(os.Stderr, "Warning: Always sign the artifact using digest(@sha256:...) rather than a tag(:%s) because tags are mutable and a tag reference can point to a different artifact than the one signed.\n", ref.Reference)
})
if err != nil {
return notation.SignOptions{}, registry.Reference{}, err
return notation.RemoteSignOptions{}, registry.Reference{}, err
}

mediaType, err := envelope.GetEnvelopeMediaType(opts.SignerFlagOpts.SignatureFormat)
if err != nil {
return notation.SignOptions{}, registry.Reference{}, err
return notation.RemoteSignOptions{}, registry.Reference{}, err
}
pluginConfig, err := cmd.ParseFlagPluginConfig(opts.pluginConfig)
pluginConfig, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name)
if err != nil {
return notation.SignOptions{}, registry.Reference{}, err
return notation.RemoteSignOptions{}, registry.Reference{}, err
}

signOpts := notation.SignOptions{
ArtifactReference: ref.String(),
SignatureMediaType: mediaType,
ExpiryDuration: opts.expiry,
PluginConfig: pluginConfig,
userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name)
if err != nil {
return notation.RemoteSignOptions{}, registry.Reference{}, err
}

signOpts := notation.RemoteSignOptions{
SignOptions: notation.SignOptions{
ArtifactReference: ref.String(),
SignatureMediaType: mediaType,
ExpiryDuration: opts.expiry,
PluginConfig: pluginConfig,
},
UserMetadata: userMetadata,
}
return signOpts, ref, nil
}
2 changes: 1 addition & 1 deletion cmd/notation/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestSignCommand_CorrectConfig(t *testing.T) {
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect sign opts: %v, got: %v", expected, opts)
}
config, err := cmd.ParseFlagPluginConfig(opts.pluginConfig)
config, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name)
if err != nil {
t.Fatalf("Parse plugin Config flag failed: %v", err)
}
Expand Down
27 changes: 25 additions & 2 deletions cmd/notation/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/notaryproject/notation-go/verifier"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/ioutil"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/spf13/cobra"
Expand All @@ -24,6 +25,7 @@ type verifyOpts struct {
SecureFlagOpts
reference string
pluginConfig []string
userMetadata []string
}

func verifyCommand(opts *verifyOpts) *cobra.Command {
Expand Down Expand Up @@ -57,6 +59,7 @@ Example - Verify a signature on an OCI artifact identified by a tag (Notation w
opts.LoggingFlagOpts.ApplyFlags(command.Flags())
opts.SecureFlagOpts.ApplyFlags(command.Flags())
command.Flags().StringArrayVarP(&opts.pluginConfig, "plugin-config", "c", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values")
cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataVerifyUsage)
return command
}

Expand Down Expand Up @@ -86,7 +89,13 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error {
}

// set up verification plugin config.
configs, err := cmd.ParseFlagPluginConfig(opts.pluginConfig)
configs, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name)
if err != nil {
return err
}

// set up user metadata
userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name)
if err != nil {
return err
}
Expand All @@ -97,14 +106,15 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error {
// TODO: need to change MaxSignatureAttempts as a user input flag or
// a field in config.json
MaxSignatureAttempts: math.MaxInt64,
UserMetadata: userMetadata,
}

// core verify process
_, outcomes, err := notation.Verify(ctx, verifier, sigRepo, verifyOpts)
// write out on failure
if err != nil || len(outcomes) == 0 {
if err != nil {
var errorVerificationFailed *notation.ErrorVerificationFailed
var errorVerificationFailed notation.ErrorVerificationFailed
if !errors.As(err, &errorVerificationFailed) {
return fmt.Errorf("signature verification failed: %w", err)
}
Expand All @@ -126,6 +136,7 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error {
fmt.Println("Trust policy is configured to skip signature verification for", ref.String())
} else {
fmt.Println("Successfully verified signature for", ref.String())
printMetadataIfPresent(outcome)
}
return nil
}
Expand All @@ -148,3 +159,15 @@ func resolveReference(ctx context.Context, opts *SecureFlagOpts, reference strin

return ref, nil
}

func printMetadataIfPresent(outcome *notation.VerificationOutcome) {
// the signature envelope is parsed as part of verification.
// since user metadata is only printed on successful verification,
// this error can be ignored
metadata, _ := outcome.UserMetadata()

if len(metadata) > 0 {
fmt.Println("\nThe artifact was signed with the following user metadata.")
ioutil.PrintMetadataMap(os.Stdout, metadata)
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.20
require (
github.com/docker/docker-credential-helpers v0.7.0
github.com/notaryproject/notation-core-go v1.0.0-rc.1
github.com/notaryproject/notation-go v1.0.0-rc.1.0.20230203031935-510def1a3f48
github.com/notaryproject/notation-go v1.0.0-rc.1.0.20230208032042-6ef3544efa06
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/sirupsen/logrus v1.9.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ github.com/notaryproject/notation-core-go v1.0.0-rc.1 h1:ACi0gr6mD1bzp9+gu3P0meJ
github.com/notaryproject/notation-core-go v1.0.0-rc.1/go.mod h1:n8Gbvl9sKa00KptkKEL5XKUyMTIALe74QipKauE2rj4=
github.com/notaryproject/notation-go v1.0.0-rc.1.0.20230203031935-510def1a3f48 h1:MHjaRqAn+uCBYkDuIGaVo91CnJY9MlTcZdYFfoE4yek=
github.com/notaryproject/notation-go v1.0.0-rc.1.0.20230203031935-510def1a3f48/go.mod h1:B/26FcjJ9GVXm1j7z+/pWKck80LdFi3KiX4Zu7gixB8=
github.com/notaryproject/notation-go v1.0.0-rc.1.0.20230208032042-6ef3544efa06 h1:0AuNQ3303yvINJSEzHUrLHSsJOyAEJvCGUit44GhERk=
github.com/notaryproject/notation-go v1.0.0-rc.1.0.20230208032042-6ef3544efa06/go.mod h1:B/26FcjJ9GVXm1j7z+/pWKck80LdFi3KiX4Zu7gixB8=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
Expand Down
22 changes: 16 additions & 6 deletions internal/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ var (
SetPflagPluginConfig = func(fs *pflag.FlagSet, p *[]string) {
fs.StringArrayVarP(p, PflagPluginConfig.Name, PflagPluginConfig.Shorthand, nil, PflagPluginConfig.Usage)
}

PflagUserMetadata = &pflag.Flag{
Name: "user-metadata",
Shorthand: "m",
}
PflagUserMetadataSignUsage = "{key}={value} pairs that are added to the signature payload"
PflagUserMetadataVerifyUsage = "user defined {key}={value} pairs that must be present in the signature for successful verification if provided"
SetPflagUserMetadata = func(fs *pflag.FlagSet, p *[]string, usage string) {
fs.StringArrayVarP(p, PflagUserMetadata.Name, PflagUserMetadata.Shorthand, nil, usage)
}
)

// KeyValueSlice is a flag with type int
Expand All @@ -79,14 +89,14 @@ type KeyValueSlice interface {
String() string
}

func ParseFlagPluginConfig(config []string) (map[string]string, error) {
pluginConfig := make(map[string]string, len(config))
for _, pair := range config {
func ParseFlagMap(c []string, flagName string) (map[string]string, error) {
m := make(map[string]string, len(c))
for _, pair := range c {
key, val, found := strings.Cut(pair, "=")
if !found || key == "" || val == "" {
return nil, fmt.Errorf("could not parse flag %s: key-value pair requires \"=\" as separator", PflagPluginConfig.Name)
return nil, fmt.Errorf("could not parse flag %s: key-value pair requires \"=\" as separator", flagName)
}
pluginConfig[key] = val
m[key] = val
}
return pluginConfig, nil
return m, nil
}
11 changes: 11 additions & 0 deletions internal/ioutil/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ func PrintKeyMap(w io.Writer, target *string, v []config.KeySuite) error {
}
return tw.Flush()
}

func PrintMetadataMap(w io.Writer, metadata map[string]string) error {
tw := newTabWriter(w)
fmt.Fprintln(tw, "\nKEY\tVALUE\t")

for k, v := range metadata {
fmt.Fprintf(tw, "%v\t%v\t\n", k, v)
}

return tw.Flush()
}

0 comments on commit 51951af

Please sign in to comment.