diff --git a/cmd/notation/key.go b/cmd/notation/key.go index 1ca8677a2..28462009b 100644 --- a/cmd/notation/key.go +++ b/cmd/notation/key.go @@ -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 } diff --git a/cmd/notation/sign.go b/cmd/notation/sign.go index 35b934496..9db9ea362 100644 --- a/cmd/notation/sign.go +++ b/cmd/notation/sign.go @@ -22,6 +22,7 @@ type signOpts struct { SecureFlagOpts expiry time.Duration pluginConfig []string + userMetadata []string reference string } @@ -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 } @@ -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 } diff --git a/cmd/notation/sign_test.go b/cmd/notation/sign_test.go index 587f9bafc..39b936b0a 100644 --- a/cmd/notation/sign_test.go +++ b/cmd/notation/sign_test.go @@ -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) } diff --git a/cmd/notation/verify.go b/cmd/notation/verify.go index c8c2d12c0..d49b94157 100644 --- a/cmd/notation/verify.go +++ b/cmd/notation/verify.go @@ -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" @@ -24,6 +25,7 @@ type verifyOpts struct { SecureFlagOpts reference string pluginConfig []string + userMetadata []string } func verifyCommand(opts *verifyOpts) *cobra.Command { @@ -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 } @@ -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 } @@ -97,6 +106,7 @@ 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 @@ -104,7 +114,7 @@ func runVerify(command *cobra.Command, opts *verifyOpts) error { // 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) } @@ -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 } @@ -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) + } +} diff --git a/go.mod b/go.mod index 2f825c0fc..7b5a1655e 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2525aad58..376669868 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/cmd/flags.go b/internal/cmd/flags.go index 53512c1be..561d206e9 100644 --- a/internal/cmd/flags.go +++ b/internal/cmd/flags.go @@ -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 @@ -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 } diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index e57e17c05..f7ae97b4d 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -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() +} \ No newline at end of file