From 4612ee5faa8bf5aeb502f25fe3ad2cd7f5e556ec Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Thu, 8 Sep 2022 09:59:54 +0000 Subject: [PATCH 1/3] feat: add policy command Signed-off-by: Binbin Li --- cmd/notation/main.go | 1 + cmd/notation/policy.go | 323 +++++++++++++++++++++++++++++++++++++++ internal/ioutil/print.go | 35 +++++ 3 files changed, 359 insertions(+) create mode 100644 cmd/notation/policy.go diff --git a/cmd/notation/main.go b/cmd/notation/main.go index bddc03568..4195d958e 100644 --- a/cmd/notation/main.go +++ b/cmd/notation/main.go @@ -22,6 +22,7 @@ func main() { listCommand(nil), certCommand(), keyCommand(), + policyCommand(), cacheCommand(), pluginCommand(), loginCommand(nil), diff --git a/cmd/notation/policy.go b/cmd/notation/policy.go new file mode 100644 index 000000000..1f32338a1 --- /dev/null +++ b/cmd/notation/policy.go @@ -0,0 +1,323 @@ +package main + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/verification" + "github.com/notaryproject/notation/internal/ioutil" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +type policyOpts struct { + configPath string + name string + scopes []string + level string + override []string + stores []string + identities []string + certPath string +} + +func policyCommand() *cobra.Command { + command := &cobra.Command{ + Use: "policy", + Short: "Manage trust policy for verification", + } + command.AddCommand( + policyListCommand(), + policyShowCommand(), + policyResolveCommand(), + policyDeleteCommand(), + policyUpdateCommand(nil), + policyAddCommand(nil), + ) + return command +} + +func policyListCommand() *cobra.Command { + return &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all configured trust policies", + RunE: func(cmd *cobra.Command, args []string) error { + return listPolicies() + }, + } +} + +func policyShowCommand() *cobra.Command { + var policyName string + return &cobra.Command{ + Use: "show ", + Short: "Inspect a policy by name", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no policy name specified") + } + policyName = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runShowCommand(cmd, policyName) + }, + } +} + +func policyResolveCommand() *cobra.Command { + var scope string + return &cobra.Command{ + Use: "resolve ", + Short: "Present policies under the given scope", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no scope specified") + } + scope = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runResolveCommand(cmd, scope) + }, + } +} + +func policyDeleteCommand() *cobra.Command { + var policyNames []string + return &cobra.Command{ + Use: "delete ...", + Short: "Delete specified policies", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no policy specified") + } + policyNames = append(policyNames, args...) + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runDeleteCommand(cmd, policyNames) + }, + } +} + +func policyUpdateCommand(opts *policyOpts) *cobra.Command { + if opts == nil { + opts = &policyOpts{} + } + command := &cobra.Command{ + Use: "update [] [|]", + Short: "Update existing policies", + Long: `Update existing policies +notation policy update path/to/config/file +notation policy update [--scope /] [--level-override :] [--trust-store :] [--identity ] + +Example - Update policies from a config file + notation policy update "/home/user/trustpolicy.json" + +Example - Update policy from options + notation policy update wabbit-networks-images \ + --scope registry.acme-rockets.io/software/net-monitor \ + --scope registry.acme-rockets.io/software/net-logger \ + --level strict \ + --trust-store ca:wabbit-networks \ + --identity "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools"`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no policy name or file path provided") + } + opts.configPath = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return updatePolicy(cmd, opts) + }, + } + + opts.ApplyFlags(command.Flags()) + return command +} + +func policyAddCommand(opts *policyOpts) *cobra.Command { + if opts == nil { + opts = &policyOpts{} + } + command := &cobra.Command{ + Use: "add []", + Short: "Add new policies", + Long: `Add new policies +notation policy add path/to/config/file +notation policy add --name --scope / --level --level-override : --trust-store : --identity [--identity-cert ] + +Example - Add policies from a config file + notation policy add "/home/user/trustpolicy.json" + +Example - Add policy from options + notation policy add --name wabbit-networks-images \ + --scope registry.acme-rockets.io/software/net-monitor \ + --scope registry.acme-rockets.io/software/net-logger \ + --level strict \ + --trust-store ca:wabbit-networks \ + --identity "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" + --identity-cert acme_root.crt`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + opts.configPath = args[0] + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return addPolicy(cmd, opts) + }, + } + + opts.ApplyFlags(command.Flags()) + command.Flags().StringVar(&opts.name, "name", "", "policy name") + // TODO: support add cert to the specified directory. + command.Flags().StringVar(&opts.certPath, "identity-cert", "", "path to the root certificate file") + return command +} + +func listPolicies() error { + policyDocument, err := verification.LoadDefaultPolicyDocument() + if err != nil { + return err + } + policies := policyDocument.ListPolicies() + + return ioutil.PrintPolicyMap(os.Stdout, policies) +} + +func runShowCommand(cmd *cobra.Command, policyName string) error { + policyDocument, err := verification.LoadDefaultPolicyDocument() + if err != nil { + return err + } + + policy, err := policyDocument.GetPolicy(policyName) + if err != nil { + return err + } + + return ioutil.PrintPolicyMap(os.Stdout, []*verification.TrustPolicy{policy}) +} + +func runResolveCommand(cmd *cobra.Command, scope string) error { + policyDocument, err := verification.LoadDefaultPolicyDocument() + if err != nil { + return err + } + + policies := policyDocument.ListPoliciesWithinScope(scope) + + return ioutil.PrintPolicyMap(os.Stdout, policies) +} + +func runDeleteCommand(cmd *cobra.Command, policyNames []string) error { + policyDocument, err := verification.LoadDefaultPolicyDocument() + if err != nil { + return err + } + + if err = policyDocument.DeletePolicies(policyNames, dir.UserLevel); err != nil { + return err + } + + ioutil.PrintDeletedPolicyNames(os.Stdout, policyNames) + return nil +} + +func updatePolicy(cmd *cobra.Command, opts *policyOpts) error { + policyDocument, err := verification.LoadDefaultPolicyDocument() + if err != nil { + return err + } + opts.prepareForUpdate() + + newPolicies, err := loadPoliciesFromOptions(opts) + if err != nil { + return err + } + + if err = policyDocument.UpdatePolicies(newPolicies, dir.UserLevel); err != nil { + return err + } + + ioutil.PrintPolicyNames(os.Stdout, newPolicies, "Updated Policies:") + return nil +} + +func addPolicy(cmd *cobra.Command, opts *policyOpts) error { + policyDocument, err := verification.LoadDefaultPolicyDocument() + if err != nil { + return err + } + + policies, err := loadPoliciesFromOptions(opts) + if err != nil { + return err + } + + if err = policyDocument.AddPolicies(policies, dir.UserLevel); err != nil { + return err + } + + ioutil.PrintPolicyNames(os.Stdout, policies, "Added Policies:") + return nil +} + +func loadPoliciesFromOptions(opts *policyOpts) ([]*verification.TrustPolicy, error) { + if opts.configPath == "" { + // create a policy from options. + policy, err := newPolicyFromOptions(opts) + if err != nil { + return nil, err + } + return []*verification.TrustPolicy{policy}, nil + } + // load policies from provided JSON file. + return verification.LoadTrustPolicies(opts.configPath) +} + +func newPolicyFromOptions(opts *policyOpts) (*verification.TrustPolicy, error) { + override := make(map[string]string) + // initialize override map from the input if provided. + for _, config := range opts.override { + idx := strings.Index(config, ":") + if idx == -1 { + return nil, fmt.Errorf("invalid override config: %s", config) + } + override[config[0:idx]] = config[idx+1:] + } + + return &verification.TrustPolicy{ + Name: opts.name, + RegistryScopes: opts.scopes, + SignatureVerification: verification.SignatureVerification{ + Level: opts.level, + Override: override, + }, + TrustStores: opts.stores, + TrustedIdentities: opts.identities, + }, nil +} + +func (opts *policyOpts) ApplyFlags(fs *pflag.FlagSet) { + fs.StringSliceVar(&opts.scopes, "scope", nil, "registry scopes") + fs.StringVar(&opts.level, "level", "", "verification level") + fs.StringSliceVar(&opts.override, "level-override", nil, "config of custom verification level") + fs.StringSliceVar(&opts.stores, "trust-store", nil, "trust stores containing trusted roots") + fs.StringArrayVar(&opts.identities, "identity", nil, "trusted identities") +} + +// prepareForUpdate checks if the update works on a file or via typed options. +func (opts *policyOpts) prepareForUpdate() { + if len(opts.scopes) != 0 || opts.level != "" || len(opts.override) != 0 || len(opts.stores) != 0 || len(opts.identities) != 0 { + opts.name = opts.configPath + opts.configPath = "" + } +} diff --git a/internal/ioutil/print.go b/internal/ioutil/print.go index 7156f9ed3..4be3c8b34 100644 --- a/internal/ioutil/print.go +++ b/internal/ioutil/print.go @@ -80,3 +80,38 @@ func printOutcomes(tw *tabwriter.Writer, outcomes []*verification.SignatureVerif fmt.Printf("%s\n\n", outcome.Error.Error()) } } + +func PrintPolicyMap(w io.Writer, v []*verification.TrustPolicy) error { + tw := newTabWriter(w) + fmt.Fprintln(tw, "NAME\tSCOPE\tVERIFICATION_LEVEL\tTRUST_STORE\tTRUSTED_IDENTITY\t") + for _, policy := range v { + fmt.Fprintf( + tw, + "%s\t%s\t%+v\t%s\t%s\n", + policy.Name, + policy.RegistryScopes, + policy.SignatureVerification, + policy.TrustStores, + policy.TrustedIdentities, + ) + } + return tw.Flush() +} + +func PrintPolicyNames(w io.Writer, v []*verification.TrustPolicy, header string) error { + tw := newTabWriter(w) + fmt.Fprintln(tw, header) + for _, policy := range v { + fmt.Fprintln(tw, policy.Name) + } + return tw.Flush() +} + +func PrintDeletedPolicyNames(w io.Writer, v []string) error { + tw := newTabWriter(w) + fmt.Fprintln(tw, "Deleted Policies:") + for _, name := range v { + fmt.Fprintln(tw, name) + } + return tw.Flush() +} From d39e671d6589c818266d31aac9ee0e953a09d123 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Tue, 27 Sep 2022 06:17:06 +0000 Subject: [PATCH 2/3] refactor: refactor policy command Signed-off-by: Binbin Li --- cmd/notation/policy.go | 115 +++++++++++++++++++++++++++-------------- docs/hello-signing.md | 2 +- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/cmd/notation/policy.go b/cmd/notation/policy.go index 1f32338a1..917df152b 100644 --- a/cmd/notation/policy.go +++ b/cmd/notation/policy.go @@ -8,6 +8,7 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/verification" + "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -18,12 +19,17 @@ type policyOpts struct { name string scopes []string level string - override []string + override string stores []string identities []string certPath string } +type deleteOpts struct { + names []string + confirmed bool +} + func policyCommand() *cobra.Command { command := &cobra.Command{ Use: "policy", @@ -33,7 +39,7 @@ func policyCommand() *cobra.Command { policyListCommand(), policyShowCommand(), policyResolveCommand(), - policyDeleteCommand(), + policyDeleteCommand(nil), policyUpdateCommand(nil), policyAddCommand(nil), ) @@ -64,7 +70,7 @@ func policyShowCommand() *cobra.Command { return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return runShowCommand(cmd, policyName) + return runShowCommand(policyName) }, } } @@ -82,27 +88,32 @@ func policyResolveCommand() *cobra.Command { return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return runResolveCommand(cmd, scope) + return runResolveCommand(scope) }, } } -func policyDeleteCommand() *cobra.Command { - var policyNames []string - return &cobra.Command{ +func policyDeleteCommand(opts *deleteOpts) *cobra.Command { + if opts == nil { + opts = &deleteOpts{} + } + command := &cobra.Command{ Use: "delete ...", Short: "Delete specified policies", Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("no policy specified") } - policyNames = append(policyNames, args...) + opts.names = append(opts.names, args...) return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return runDeleteCommand(cmd, policyNames) + return runDeleteCommand(opts) }, } + command.Flags().BoolVarP(&opts.confirmed, "confirm", "y", false, "if yes, do not prompt for confirmation") + + return command } func policyUpdateCommand(opts *policyOpts) *cobra.Command { @@ -114,7 +125,7 @@ func policyUpdateCommand(opts *policyOpts) *cobra.Command { Short: "Update existing policies", Long: `Update existing policies notation policy update path/to/config/file -notation policy update [--scope /] [--level-override :] [--trust-store :] [--identity ] +notation policy update [--scope /] [--level-override =] [--trust-store :] [--identity ] Example - Update policies from a config file notation policy update "/home/user/trustpolicy.json" @@ -133,12 +144,15 @@ Example - Update policy from options opts.configPath = args[0] return nil }, + PreRun: func(cmd *cobra.Command, args []string) { + trimSpace(opts) + }, RunE: func(cmd *cobra.Command, args []string) error { - return updatePolicy(cmd, opts) + return updatePolicy(opts) }, } - opts.ApplyFlags(command.Flags()) + opts.applyFlags(command.Flags()) return command } @@ -151,7 +165,7 @@ func policyAddCommand(opts *policyOpts) *cobra.Command { Short: "Add new policies", Long: `Add new policies notation policy add path/to/config/file -notation policy add --name --scope / --level --level-override : --trust-store : --identity [--identity-cert ] +notation policy add --name --scope / --level --level-override = --trust-store : --identity [--identity-cert ] Example - Add policies from a config file notation policy add "/home/user/trustpolicy.json" @@ -165,20 +179,27 @@ Example - Add policy from options --identity "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" --identity-cert acme_root.crt`, Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { + if len(args) == 1 { opts.configPath = args[0] + } else if len(args) == 0 { + return errors.New("no arguments provided") + } else { + fmt.Println("multiple files provided, only the first one will be honored") } return nil }, + PreRun: func(cmd *cobra.Command, args []string) { + trimSpace(opts) + }, RunE: func(cmd *cobra.Command, args []string) error { - return addPolicy(cmd, opts) + return addPolicy(opts) }, } - opts.ApplyFlags(command.Flags()) + opts.applyFlags(command.Flags()) command.Flags().StringVar(&opts.name, "name", "", "policy name") // TODO: support add cert to the specified directory. - command.Flags().StringVar(&opts.certPath, "identity-cert", "", "path to the root certificate file") + command.Flags().StringVar(&opts.certPath, "identity-cert", "", "path to the certificate file") return command } @@ -192,7 +213,7 @@ func listPolicies() error { return ioutil.PrintPolicyMap(os.Stdout, policies) } -func runShowCommand(cmd *cobra.Command, policyName string) error { +func runShowCommand(policyName string) error { policyDocument, err := verification.LoadDefaultPolicyDocument() if err != nil { return err @@ -206,7 +227,7 @@ func runShowCommand(cmd *cobra.Command, policyName string) error { return ioutil.PrintPolicyMap(os.Stdout, []*verification.TrustPolicy{policy}) } -func runResolveCommand(cmd *cobra.Command, scope string) error { +func runResolveCommand(scope string) error { policyDocument, err := verification.LoadDefaultPolicyDocument() if err != nil { return err @@ -217,28 +238,37 @@ func runResolveCommand(cmd *cobra.Command, scope string) error { return ioutil.PrintPolicyMap(os.Stdout, policies) } -func runDeleteCommand(cmd *cobra.Command, policyNames []string) error { +func runDeleteCommand(opts *deleteOpts) error { + prompt := fmt.Sprintf("Are you sure you want to delete policies: %s ?", strings.Join(opts.names, ", ")) + confirmed, err := ioutil.AskForConfirmation(os.Stdin, prompt, opts.confirmed) + if err != nil { + return err + } + if !confirmed { + return nil + } + policyDocument, err := verification.LoadDefaultPolicyDocument() if err != nil { return err } - if err = policyDocument.DeletePolicies(policyNames, dir.UserLevel); err != nil { + if err = policyDocument.DeletePolicies(opts.names, dir.UserLevel); err != nil { return err } - ioutil.PrintDeletedPolicyNames(os.Stdout, policyNames) + ioutil.PrintDeletedPolicyNames(os.Stdout, opts.names) return nil } -func updatePolicy(cmd *cobra.Command, opts *policyOpts) error { - policyDocument, err := verification.LoadDefaultPolicyDocument() +func updatePolicy(opts *policyOpts) error { + opts.prepareForUpdate() + newPolicies, err := loadPoliciesFromOptions(opts) if err != nil { return err } - opts.prepareForUpdate() - newPolicies, err := loadPoliciesFromOptions(opts) + policyDocument, err := verification.LoadDefaultPolicyDocument() if err != nil { return err } @@ -251,13 +281,13 @@ func updatePolicy(cmd *cobra.Command, opts *policyOpts) error { return nil } -func addPolicy(cmd *cobra.Command, opts *policyOpts) error { - policyDocument, err := verification.LoadDefaultPolicyDocument() +func addPolicy(opts *policyOpts) error { + policies, err := loadPoliciesFromOptions(opts) if err != nil { return err } - policies, err := loadPoliciesFromOptions(opts) + policyDocument, err := verification.LoadDefaultPolicyDocument() if err != nil { return err } @@ -271,7 +301,7 @@ func addPolicy(cmd *cobra.Command, opts *policyOpts) error { } func loadPoliciesFromOptions(opts *policyOpts) ([]*verification.TrustPolicy, error) { - if opts.configPath == "" { + if len(opts.configPath) == 0 { // create a policy from options. policy, err := newPolicyFromOptions(opts) if err != nil { @@ -284,16 +314,16 @@ func loadPoliciesFromOptions(opts *policyOpts) ([]*verification.TrustPolicy, err } func newPolicyFromOptions(opts *policyOpts) (*verification.TrustPolicy, error) { - override := make(map[string]string) - // initialize override map from the input if provided. - for _, config := range opts.override { - idx := strings.Index(config, ":") - if idx == -1 { - return nil, fmt.Errorf("invalid override config: %s", config) - } - override[config[0:idx]] = config[idx+1:] + if len(opts.name) == 0 { + return nil, errors.New("no name specified") + } + override, err := cmd.ParseFlagPluginConfig(opts.override) + if err != nil { + return nil, err } + // TODO: check the default values of each fields once spec is available. + return &verification.TrustPolicy{ Name: opts.name, RegistryScopes: opts.scopes, @@ -306,10 +336,10 @@ func newPolicyFromOptions(opts *policyOpts) (*verification.TrustPolicy, error) { }, nil } -func (opts *policyOpts) ApplyFlags(fs *pflag.FlagSet) { +func (opts *policyOpts) applyFlags(fs *pflag.FlagSet) { fs.StringSliceVar(&opts.scopes, "scope", nil, "registry scopes") fs.StringVar(&opts.level, "level", "", "verification level") - fs.StringSliceVar(&opts.override, "level-override", nil, "config of custom verification level") + fs.StringVar(&opts.override, "level-coverride", "", "list of comma-separated {key}={value} pairs that override the behavior of the verification level") fs.StringSliceVar(&opts.stores, "trust-store", nil, "trust stores containing trusted roots") fs.StringArrayVar(&opts.identities, "identity", nil, "trusted identities") } @@ -321,3 +351,8 @@ func (opts *policyOpts) prepareForUpdate() { opts.configPath = "" } } + +func trimSpace(opts *policyOpts) { + opts.configPath = strings.TrimSpace(opts.configPath) + opts.name = strings.TrimSpace(opts.name) +} diff --git a/docs/hello-signing.md b/docs/hello-signing.md index 82435ae1f..b0777ba2c 100644 --- a/docs/hello-signing.md +++ b/docs/hello-signing.md @@ -107,7 +107,7 @@ To get things started quickly, the Notation cli supports self-signed certificate ## Verify a Container Image Using Notation Signatures -Notation provides a trust policy [`~/.config/notation/trustpolicy.json`] for users to specify trusted identities which will sign the artifiacts, and level of signature verification to use. A trust policy is a JSON document, below example works for the current case. +Notation provides a trust policy for users to specify trusted identities which will sign the artifiacts, and level of signature verification to use. A trust policy is a JSON document, below example works for the current case. ``` { From e41bb47403d10e245008a8617073355193d0db7d Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Fri, 30 Sep 2022 08:54:19 +0000 Subject: [PATCH 3/3] test: add unit test Signed-off-by: Binbin Li --- cmd/notation/policy.go | 31 +++++- cmd/notation/policy_test.go | 197 ++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 cmd/notation/policy_test.go diff --git a/cmd/notation/policy.go b/cmd/notation/policy.go index 917df152b..ea244cf8e 100644 --- a/cmd/notation/policy.go +++ b/cmd/notation/policy.go @@ -8,6 +8,7 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/verification" + "github.com/notaryproject/notation/internal/certificate" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/ioutil" "github.com/spf13/cobra" @@ -181,9 +182,7 @@ Example - Add policy from options Args: func(cmd *cobra.Command, args []string) error { if len(args) == 1 { opts.configPath = args[0] - } else if len(args) == 0 { - return errors.New("no arguments provided") - } else { + } else if len(args) > 1 { fmt.Println("multiple files provided, only the first one will be honored") } return nil @@ -198,7 +197,6 @@ Example - Add policy from options opts.applyFlags(command.Flags()) command.Flags().StringVar(&opts.name, "name", "", "policy name") - // TODO: support add cert to the specified directory. command.Flags().StringVar(&opts.certPath, "identity-cert", "", "path to the certificate file") return command } @@ -296,6 +294,10 @@ func addPolicy(opts *policyOpts) error { return err } + if err = addCertificate(opts.certPath, opts.stores); err != nil { + return err + } + ioutil.PrintPolicyNames(os.Stdout, policies, "Added Policies:") return nil } @@ -339,7 +341,7 @@ func newPolicyFromOptions(opts *policyOpts) (*verification.TrustPolicy, error) { func (opts *policyOpts) applyFlags(fs *pflag.FlagSet) { fs.StringSliceVar(&opts.scopes, "scope", nil, "registry scopes") fs.StringVar(&opts.level, "level", "", "verification level") - fs.StringVar(&opts.override, "level-coverride", "", "list of comma-separated {key}={value} pairs that override the behavior of the verification level") + fs.StringVar(&opts.override, "level-override", "", "list of comma-separated {key}={value} pairs that override the behavior of the verification level") fs.StringSliceVar(&opts.stores, "trust-store", nil, "trust stores containing trusted roots") fs.StringArrayVar(&opts.identities, "identity", nil, "trusted identities") } @@ -355,4 +357,23 @@ func (opts *policyOpts) prepareForUpdate() { func trimSpace(opts *policyOpts) { opts.configPath = strings.TrimSpace(opts.configPath) opts.name = strings.TrimSpace(opts.name) + opts.certPath = strings.TrimSpace(opts.certPath) } + +func addCertificate(certPath string, stores []string) error { + if len(certPath) == 0 { + return nil + } + + for _, store := range stores { + parts := strings.Split(store, ":") + if len(parts) != 2 { + return fmt.Errorf("trust store: %s is invalid, the valid format is: type:name", store) + } + if err := certificate.AddCertCore(certPath, parts[0], parts[1], false); err != nil { + return err + } + } + + return nil +} \ No newline at end of file diff --git a/cmd/notation/policy_test.go b/cmd/notation/policy_test.go new file mode 100644 index 000000000..994f4340c --- /dev/null +++ b/cmd/notation/policy_test.go @@ -0,0 +1,197 @@ +package main + +import ( + "reflect" + "testing" +) + +const ( + testPolicyName = "test_policy_name" + testScope = "test_scope" + testPath = "test_path" + testLevel = "test_level" + testOverride = "key=val" + testTrustStore = "ca:test" + testIdentity = "testIdentity" +) + +func TestPolicyListCommand_BasicArgs(t *testing.T) { + cmd := policyListCommand() + if err := cmd.ParseFlags([]string{}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } +} + +func TestPolicyShowCommand_ValidArgs(t *testing.T) { + cmd := policyShowCommand() + if err := cmd.ParseFlags([]string{testPolicyName}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } +} + +func TestPolicyShowCommand_NoArg(t *testing.T) { + cmd := policyShowCommand() + if err := cmd.ParseFlags([]string{}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Error should be returned but got nil") + } +} + +func TestPolicyResolveCommand_ValidArgs(t *testing.T) { + cmd := policyResolveCommand() + if err := cmd.ParseFlags([]string{testScope}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } +} + +func TestPolicyResolveCommand_NoArgs(t *testing.T) { + cmd := policyResolveCommand() + if err := cmd.ParseFlags([]string{}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Error should be returned but got nil") + } +} + +func TestPolicyDeleteCommand_ValidArgs(t *testing.T) { + opts := &deleteOpts{} + cmd := policyDeleteCommand(opts) + expected := &deleteOpts{ + names: []string{testPolicyName}, + confirmed: false, + } + if err := cmd.ParseFlags(expected.names); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(opts, expected) { + t.Fatalf("Expect opts: %+v, got: %+v", expected, opts) + } +} + +func TestPolicyDeleteCommand_NoArgs(t *testing.T) { + opts := &deleteOpts{} + cmd := policyDeleteCommand(opts) + if err := cmd.ParseFlags([]string{}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Error should be returned but got nil") + } +} + +func TestPolicyUpdateCommand_NoArgs(t *testing.T) { + opts := &policyOpts{} + cmd := policyUpdateCommand(opts) + if err := cmd.ParseFlags([]string{}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { + t.Fatal("Error should be returned but got nil") + } +} + +func TestPolicyUpdateCommand_FilePath(t *testing.T) { + opts := &policyOpts{} + cmd := policyUpdateCommand(opts) + expected := &policyOpts{ + configPath: testPath, + } + if err := cmd.ParseFlags([]string{testPath}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(opts, expected) { + t.Fatalf("Expect opts: %+v, got: %+v", expected, opts) + } +} + +func TestPolicyUpdateCommand_Flags(t *testing.T) { + opts := &policyOpts{} + cmd := policyUpdateCommand(opts) + expected := &policyOpts{ + configPath: testPolicyName, + scopes: []string{testScope}, + level: testLevel, + override: testOverride, + stores: []string{testTrustStore}, + identities: []string{testIdentity}, + } + if err := cmd.ParseFlags([]string{ + "--scope", testScope, + "--level", testLevel, + "--level-override", testOverride, + "--trust-store", testTrustStore, + "--identity", testIdentity, + testPolicyName, + }); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(opts, expected) { + t.Fatalf("Expect opts: %+v, got: %+v", expected, opts) + } +} + +func TestPolicyAddCommand_FilePath(t *testing.T) { + opts := &policyOpts{} + cmd := policyAddCommand(opts) + expected := &policyOpts{ + configPath: testPath, + } + if err := cmd.ParseFlags([]string{testPath}); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(opts, expected) { + t.Fatalf("Expect opts: %+v, got: %+v", expected, opts) + } +} + +func TestPolicyAddCommand_Flags(t *testing.T) { + opts := &policyOpts{} + cmd := policyAddCommand(opts) + expected := &policyOpts{ + configPath: testPolicyName, + scopes: []string{testScope}, + level: testLevel, + override: testOverride, + stores: []string{testTrustStore}, + identities: []string{testIdentity}, + certPath: testPath, + } + if err := cmd.ParseFlags([]string{ + "--scope", testScope, + "--level", testLevel, + "--level-override", testOverride, + "--trust-store", testTrustStore, + "--identity", testIdentity, + "--identity-cert", testPath, + testPolicyName, + }); err != nil { + t.Fatalf("Parse flag failed: %v", err) + } + if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil { + t.Fatalf("Parse args failed: %v", err) + } + if !reflect.DeepEqual(opts, expected) { + t.Fatalf("Expect opts: %+v, got: %+v", expected, opts) + } +} \ No newline at end of file