From 2dcf2e33b15582e2d5c5a10c34ea43c5a1358870 Mon Sep 17 00:00:00 2001 From: Manjusha Sonawane Date: Wed, 7 May 2025 15:00:41 +0530 Subject: [PATCH 1/9] Added support for personal certificates to Create, Get, Delete and Update certificates to pkg/cmd module in verifyctl --- pkg/cmd/create/certificate.go | 192 +++++++++++++++++++++++++++++++++ pkg/cmd/create/create.go | 6 ++ pkg/cmd/delete/certificate.go | 108 +++++++++++++++++++ pkg/cmd/delete/delete.go | 1 + pkg/cmd/get/certificate.go | 179 ++++++++++++++++++++++++++++++ pkg/cmd/get/get.go | 1 + pkg/cmd/replace/certificate.go | 169 +++++++++++++++++++++++++++++ pkg/cmd/replace/replace.go | 5 + 8 files changed, 661 insertions(+) create mode 100644 pkg/cmd/create/certificate.go create mode 100644 pkg/cmd/delete/certificate.go create mode 100644 pkg/cmd/get/certificate.go create mode 100644 pkg/cmd/replace/certificate.go diff --git a/pkg/cmd/create/certificate.go b/pkg/cmd/create/certificate.go new file mode 100644 index 0000000..b07f609 --- /dev/null +++ b/pkg/cmd/create/certificate.go @@ -0,0 +1,192 @@ +package create + +import ( + "encoding/json" + "io" + "os" + + "github.com/ibm-verify/verify-sdk-go/pkg/config/security" + contextx "github.com/ibm-verify/verify-sdk-go/pkg/core/context" + errorsx "github.com/ibm-verify/verify-sdk-go/pkg/core/errors" + "github.com/ibm-verify/verify-sdk-go/pkg/i18n" + "github.com/ibm-verify/verifyctl/pkg/cmd/resource" + "github.com/ibm-verify/verifyctl/pkg/config" + cmdutil "github.com/ibm-verify/verifyctl/pkg/util/cmd" + "github.com/ibm-verify/verifyctl/pkg/util/templates" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +const ( + personalCertUsage = `personalCert [options]` + personalCertMessagePrefix = "CreatepersonalCert" + personalCertEntitlements = "Manage password policies" + personalCertResourceName = "personalCert" +) + +var ( + personalCertShortDesc = cmdutil.TranslateShortDesc( + personalCertMessagePrefix, + "Create a personal certificate with specified options.", + ) + personalCertLongDesc = templates.LongDesc( + cmdutil.TranslateLongDesc( + personalCertMessagePrefix, + `Create a personal certificate resource. Resources managed on Verify require specific entitlements. + Ensure the application or API client used with the 'auth' command has the "Manage password policies" entitlement. + Generate an empty resource file with: verifyctl create personalCert --boilerplate + Check required entitlements with: verifyctl create personalCert --entitlements + Input files can be in YAML or JSON format.`, + ), + ) + personalCertExamples = templates.Examples( + cmdutil.TranslateExamples( + personalCertMessagePrefix, + `# Create an empty personal certificate resource. + verifyctl create personalCert --boilerplate + # Create a personal certificate using a YAML file. + verifyctl create personalCert -f=./personal_cert.yaml + # Create a personal certificate using a JSON file. + verifyctl create personalCert -f=./personal_cert.json`, + ), + ) +) + +type personalCertOptions struct { + options + file string +} + +func newPersonalCertCommand(config *config.CLIConfig, streams io.ReadWriter) *cobra.Command { + o := &personalCertOptions{ + options: options{ + config: config, + }, + } + + cmd := &cobra.Command{ + Use: personalCertUsage, + Short: personalCertShortDesc, + Long: personalCertLongDesc, + Example: personalCertExamples, + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.ExitOnError(cmd, o.Complete(cmd, args)) + cmdutil.ExitOnError(cmd, o.Validate(cmd, args)) + cmdutil.ExitOnError(cmd, o.Run(cmd, args)) + }, + } + + cmd.SetOut(streams) + cmd.SetErr(streams) + cmd.SetIn(streams) + + o.AddFlags(cmd) + + return cmd +} + +func (o *personalCertOptions) AddFlags(cmd *cobra.Command) { + o.addCommonFlags(cmd, personalCertResourceName) + cmd.Flags().StringVarP(&o.file, "file", "f", "", i18n.Translate("Path to the YAML file that contains the input data.")) +} + +func (o *personalCertOptions) Complete(cmd *cobra.Command, args []string) error { + return nil +} + +func (o *personalCertOptions) Validate(cmd *cobra.Command, args []string) error { + if o.entitlements || o.boilerplate { + return nil + } + + if len(o.file) == 0 { + return errorsx.G11NError(i18n.Translate("'file' option is required if no other options are used.")) + } + return nil +} + +func (o *personalCertOptions) Run(cmd *cobra.Command, args []string) error { + if o.entitlements { + cmdutil.WriteString(cmd, entitlementsMessage+" "+personalCertEntitlements) + return nil + } + + if o.boilerplate { + resourceObj := &resource.ResourceObject{ + Kind: resource.ResourceTypePrefix + "PersonalCert", + APIVersion: "1.0", + Data: &security.PersonalCert{}, + } + + cmdutil.WriteAsYAML(cmd, resourceObj, cmd.OutOrStdout()) + return nil + } + + _, err := o.config.SetAuthToContext(cmd.Context()) + if err != nil { + return err + } + + return o.createPersonalCert(cmd) +} + +func (o *personalCertOptions) createPersonalCert(cmd *cobra.Command) error { + ctx := cmd.Context() + vc := contextx.GetVerifyContext(ctx) + + b, err := os.ReadFile(o.file) + if err != nil { + vc.Logger.Errorf("unable to read file; filename=%s, err=%v", o.file, err) + return err + } + + return o.createPersonalCertWithData(cmd, b) +} + +func (o *personalCertOptions) createPersonalCertWithData(cmd *cobra.Command, data []byte) error { + ctx := cmd.Context() + vc := contextx.GetVerifyContext(ctx) + + personalCert := &security.PersonalCert{} + if err := yaml.Unmarshal(data, &personalCert); err != nil { + vc.Logger.Errorf("unable to unmarshal the personalCert; err=%v", err) + return err + } + + client := security.NewPersonalCertClient() + resourceURI, err := client.CreatePersonalCert(ctx, personalCert) + if err != nil { + return err + } + + cmdutil.WriteString(cmd, "Resource created: "+resourceURI) + return nil +} + +func (o *personalCertOptions) createPersonalCertFromDataMap(cmd *cobra.Command, data map[string]interface{}) error { + ctx := cmd.Context() + vc := contextx.GetVerifyContext(ctx) + + personalCert := &security.PersonalCert{} + b, err := json.Marshal(data) + if err != nil { + vc.Logger.Errorf("failed to marshal the data map into json; err=%v", err) + return err + } + + if err := json.Unmarshal(b, personalCert); err != nil { + vc.Logger.Errorf("unable to unmarshal to an personalCert; err=%v", err) + return err + } + + client := security.NewPersonalCertClient() + resourceURI, err := client.CreatePersonalCert(ctx, personalCert) + if err != nil { + vc.Logger.Errorf("unable to create the password policy; err=%v, personalCert=%+v", err, personalCert) + return err + } + + cmdutil.WriteString(cmd, "Resource created: "+resourceURI) + return nil +} diff --git a/pkg/cmd/create/create.go b/pkg/cmd/create/create.go index 4817891..1ed6d5e 100644 --- a/pkg/cmd/create/create.go +++ b/pkg/cmd/create/create.go @@ -94,6 +94,7 @@ func NewCommand(config *config.CLIConfig, streams io.ReadWriter, groupID string) cmd.AddCommand(newAPIClientCommand(config, streams)) cmd.AddCommand(newApplicationCommand(config, streams)) cmd.AddCommand(newPasswordPolicyCommand(config, streams)) + cmd.AddCommand(newPersonalCertCommand(config, streams)) return cmd } @@ -164,6 +165,11 @@ func (o *options) Run(cmd *cobra.Command, args []string) error { case resource.ResourceTypePrefix + "PasswordPolicy": options := &passwordPolicyOptions{} err = options.createPasswordPolicyFromDataMap(cmd, resourceObject.Data.(map[string]interface{})) + + case resource.ResourceTypePrefix + "PersonalCert": + options := &personalCertOptions{} + err = options.createPersonalCertFromDataMap(cmd, resourceObject.Data.(map[string]interface{})) + } return err diff --git a/pkg/cmd/delete/certificate.go b/pkg/cmd/delete/certificate.go new file mode 100644 index 0000000..e18521f --- /dev/null +++ b/pkg/cmd/delete/certificate.go @@ -0,0 +1,108 @@ +package delete + +import ( + "io" + + "github.com/ibm-verify/verify-sdk-go/pkg/config/security" + errorsx "github.com/ibm-verify/verify-sdk-go/pkg/core/errors" + "github.com/ibm-verify/verify-sdk-go/pkg/i18n" + "github.com/ibm-verify/verifyctl/pkg/config" + cmdutil "github.com/ibm-verify/verifyctl/pkg/util/cmd" + "github.com/ibm-verify/verifyctl/pkg/util/templates" + "github.com/spf13/cobra" +) + +const ( + personalCertUsage = `personalCert [options]` + personalCertMessagePrefix = "DeletePersonalCert" + personalCertEntitlements = "Manage Personal Certs" + personalCertResourceName = "personalCert" +) + +var ( + personalCertLongDesc = templates.LongDesc(cmdutil.TranslateLongDesc(personalCertMessagePrefix, ` + Delete a personal certificate in IBM Security Verify based on label. + Resources managed on Verify have specific entitlements, so ensure that the application or API client used with the 'auth' command is configured with the appropriate entitlements. + You can identify the entitlement required by running: verifyctl delete personalCert --entitlements`)) + + personalCertExamples = templates.Examples(cmdutil.TranslateExamples(personalCertMessagePrefix, ` + # Delete a personal certificate by label + verifyctl delete personalCert --name=certificateLabel + `)) +) + +type personalCertOptions struct { + options + config *config.CLIConfig + label string +} + +func NewPersonalCertCommand(config *config.CLIConfig, streams io.ReadWriter) *cobra.Command { + o := &personalCertOptions{ + config: config, + } + cmd := &cobra.Command{ + Use: personalCertUsage, + Short: cmdutil.TranslateShortDesc(personalCertMessagePrefix, "Delete Verify personal certificate based on a label."), + Long: personalCertLongDesc, + Example: personalCertExamples, + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.ExitOnError(cmd, o.Complete(cmd, args)) + cmdutil.ExitOnError(cmd, o.Validate(cmd, args)) + cmdutil.ExitOnError(cmd, o.Run(cmd, args)) + }, + } + cmd.SetOut(streams) + cmd.SetErr(streams) + cmd.SetIn(streams) + o.AddFlags(cmd) + return cmd +} + +func (o *personalCertOptions) AddFlags(cmd *cobra.Command) { + o.addCommonFlags(cmd) + cmd.Flags().StringVar(&o.label, "personalCertLabel", o.label, i18n.Translate("Label of the personal certificate to delete. (Required)")) +} + +func (o *personalCertOptions) Complete(cmd *cobra.Command, args []string) error { + return nil +} + +func (o *personalCertOptions) Validate(cmd *cobra.Command, args []string) error { + if o.entitlements { + return nil + } + calledAs := cmd.CalledAs() + if calledAs == "personalCert" && o.label == "" { + return errorsx.G11NError(i18n.Translate("The 'name' flag is required to delete a personal certificate")) + } + return nil +} + +func (o *personalCertOptions) Run(cmd *cobra.Command, args []string) error { + if o.entitlements { + cmdutil.WriteString(cmd, entitlementsMessage+" "+personalCertEntitlements) + return nil + } + + _, err := o.config.SetAuthToContext(cmd.Context()) + if err != nil { + return err + } + + if cmd.CalledAs() == "personalCert" || len(o.label) > 0 { + return o.handleSinglePersonalCert(cmd, args) + } + return nil +} + +func (o *personalCertOptions) handleSinglePersonalCert(cmd *cobra.Command, _ []string) error { + c := security.NewPersonalCertClient() + err := c.DeletePersonalCert(cmd.Context(), o.label) + if err != nil { + return err + } + cmdutil.WriteString(cmd, "Resource deleted: "+o.label) + return nil +} diff --git a/pkg/cmd/delete/delete.go b/pkg/cmd/delete/delete.go index 4ff5f90..858d886 100644 --- a/pkg/cmd/delete/delete.go +++ b/pkg/cmd/delete/delete.go @@ -75,6 +75,7 @@ func NewCommand(config *config.CLIConfig, streams io.ReadWriter, groupID string) cmd.AddCommand(NewAPIClientCommand(config, streams)) cmd.AddCommand(NewApplicationCommand(config, streams)) cmd.AddCommand(NewPasswordPolicyCommand(config, streams)) + cmd.AddCommand(NewPersonalCertCommand(config, streams)) return cmd } diff --git a/pkg/cmd/get/certificate.go b/pkg/cmd/get/certificate.go new file mode 100644 index 0000000..9825058 --- /dev/null +++ b/pkg/cmd/get/certificate.go @@ -0,0 +1,179 @@ +package get + +import ( + "io" + + "github.com/ibm-verify/verify-sdk-go/pkg/config/security" + errorsx "github.com/ibm-verify/verify-sdk-go/pkg/core/errors" + "github.com/ibm-verify/verify-sdk-go/pkg/i18n" + "github.com/ibm-verify/verifyctl/pkg/cmd/resource" + "github.com/ibm-verify/verifyctl/pkg/config" + cmdutil "github.com/ibm-verify/verifyctl/pkg/util/cmd" + "github.com/ibm-verify/verifyctl/pkg/util/templates" + "github.com/spf13/cobra" +) + +const ( + personalCertUsage = `personalCerts [flags]` + personalCertMessagePrefix = "GetPersonalCerts" + personalCertEntitlements = "Manage Personal Certs" + personalCertResourceName = "personalCert" +) + +var ( + personalCertLongDesc = templates.LongDesc(cmdutil.TranslateLongDesc(personalCertMessagePrefix, ` +Get Verify personal certificate based on an optional filter or a specific personal certificate. + +Resources managed on Verify have specific entitlements, so ensure that the application or API client used +with the 'auth' command is configured with the appropriate entitlements. + +You can identify the entitlement required by running: + +verifyctl get personalCert --entitlements`)) + + personalCertExamples = templates.Examples(cmdutil.TranslateExamples(personalCertMessagePrefix, ` +# Get a specific personal certificate by lable +verifyctl get personalCert -o=yaml --name=testpersonalCert + +# Get 10 policies based on a given search criteria and sort it in the ascending order by name. + verifyctl get personal-certificate --count=2 --sort=label -o=yaml +`)) +) + +type personalCertOptions struct { + options + + config *config.CLIConfig + label string +} + +func newPersonalCertCommand(config *config.CLIConfig, streams io.ReadWriter) *cobra.Command { + o := &personalCertOptions{ + config: config, + } + + cmd := &cobra.Command{ + Use: personalCertUsage, + Short: cmdutil.TranslateShortDesc(personalCertMessagePrefix, "Get Verify password policies based on an optional filter or a specific policy."), + Long: personalCertLongDesc, + Example: personalCertExamples, + Aliases: []string{"personalCert"}, + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.ExitOnError(cmd, o.Complete(cmd, args)) + cmdutil.ExitOnError(cmd, o.Validate(cmd, args)) + cmdutil.ExitOnError(cmd, o.Run(cmd, args)) + }, + } + + cmd.SetOut(streams) + cmd.SetErr(streams) + cmd.SetIn(streams) + + o.AddFlags(cmd) + + return cmd +} + +func (o *personalCertOptions) AddFlags(cmd *cobra.Command) { + o.addCommonFlags(cmd, personalCertResourceName) + cmd.Flags().StringVar(&o.label, "personalCertLabel", o.label, i18n.Translate("personalCertName to get details")) + o.addSortFlags(cmd, personalCertResourceName) + o.addCountFlags(cmd, personalCertResourceName) +} + +func (o *personalCertOptions) Complete(cmd *cobra.Command, args []string) error { + return nil +} + +func (o *personalCertOptions) Validate(cmd *cobra.Command, args []string) error { + if o.entitlements { + return nil + } + calledAs := cmd.CalledAs() + if calledAs == "personalCert" && o.label == "" { + return errorsx.G11NError(i18n.Translate("'personalCertName' flag is required.")) + } + return nil +} + +func (o *personalCertOptions) Run(cmd *cobra.Command, args []string) error { + if o.entitlements { + cmdutil.WriteString(cmd, entitlementsMessage+" "+personalCertEntitlements) + return nil + } + _, err := o.config.SetAuthToContext(cmd.Context()) + if err != nil { + return err + } + if cmd.CalledAs() == "personalCert" || len(o.label) > 0 { + return o.handleSinglePersonalCert(cmd, args) + } + return o.handlePersonalCertList(cmd, args) +} + +func (o *personalCertOptions) handleSinglePersonalCert(cmd *cobra.Command, _ []string) error { + + c := security.NewPersonalCertClient() + pcrt, uri, err := c.GetPersonalCert(cmd.Context(), o.label) + if err != nil { + return err + } + if o.output == "raw" { + cmdutil.WriteAsJSON(cmd, pcrt, cmd.OutOrStdout()) + return nil + } + resourceObj := &resource.ResourceObject{ + Kind: resource.ResourceTypePrefix + "PersonalCert", + APIVersion: "1.0", + Metadata: &resource.ResourceObjectMetadata{ + Name: pcrt.Label, + URI: uri, + }, + Data: pcrt, + } + if o.output == "json" { + cmdutil.WriteAsJSON(cmd, resourceObj, cmd.OutOrStdout()) + } else { + cmdutil.WriteAsYAML(cmd, resourceObj, cmd.OutOrStdout()) + } + return nil +} + +func (o *personalCertOptions) handlePersonalCertList(cmd *cobra.Command, _ []string) error { + c := security.NewPersonalCertClient() + pcrts, uri, err := c.GetPersonalCerts(cmd.Context(), o.sort, o.count) + if err != nil { + return err + } + if o.output == "raw" { + cmdutil.WriteAsJSON(cmd, pcrts, cmd.OutOrStdout()) + return nil + } + + items := []*resource.ResourceObject{} + for _, pcrt := range pcrts.PersonalCerts { + items = append(items, &resource.ResourceObject{ + Kind: resource.ResourceTypePrefix + "PersonalCert", + APIVersion: "1.0", + Metadata: &resource.ResourceObjectMetadata{ + Name: pcrt.Label, + }, + Data: pcrt, + }) + } + resourceObj := &resource.ResourceObjectList{ + Kind: resource.ResourceTypePrefix + "List", + APIVersion: "1.0", + Metadata: &resource.ResourceObjectMetadata{ + URI: uri, + }, + Items: items, + } + if o.output == "raw" || o.output == "json" { + cmdutil.WriteAsJSON(cmd, resourceObj, cmd.OutOrStdout()) + } else { + cmdutil.WriteAsYAML(cmd, resourceObj, cmd.OutOrStdout()) + } + return nil +} diff --git a/pkg/cmd/get/get.go b/pkg/cmd/get/get.go index b82b027..0ff5d0c 100644 --- a/pkg/cmd/get/get.go +++ b/pkg/cmd/get/get.go @@ -88,6 +88,7 @@ func NewCommand(config *config.CLIConfig, streams io.ReadWriter, groupID string) cmd.AddCommand(NewAPIClientsCommand(config, streams)) cmd.AddCommand(NewApplicationsCommand(config, streams)) cmd.AddCommand(newPasswordPolicyCommand(config, streams)) + cmd.AddCommand(newPersonalCertCommand(config, streams)) return cmd } diff --git a/pkg/cmd/replace/certificate.go b/pkg/cmd/replace/certificate.go new file mode 100644 index 0000000..2f148e8 --- /dev/null +++ b/pkg/cmd/replace/certificate.go @@ -0,0 +1,169 @@ +package replace + +import ( + "io" + "os" + + "github.com/ibm-verify/verify-sdk-go/pkg/config/security" + contextx "github.com/ibm-verify/verify-sdk-go/pkg/core/context" + errorsx "github.com/ibm-verify/verify-sdk-go/pkg/core/errors" + "github.com/ibm-verify/verify-sdk-go/pkg/i18n" + "github.com/ibm-verify/verifyctl/pkg/cmd/resource" + "github.com/ibm-verify/verifyctl/pkg/config" + cmdutil "github.com/ibm-verify/verifyctl/pkg/util/cmd" + "github.com/ibm-verify/verifyctl/pkg/util/templates" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +const ( + personalCertUsage = `personalCert [options]` + personalCertMessagePrefix = "UpdatePersonalCert" + personalCertEntitlements = "Manage Personal Certs" + personalCertResourceName = "personalCert" +) + +var ( + personalCertShortDesc = cmdutil.TranslateShortDesc(personalCertMessagePrefix, "Update a personal certificate resource.") + + personalCertLongDesc = templates.LongDesc(cmdutil.TranslateLongDesc(personalCertMessagePrefix, ` + Update a personal certificate resource. + Resources managed on Verify require specific entitlements, so ensure that the application or API client used with the 'auth' command is configured with the appropriate entitlements. + An empty resource file can be generated using: verifyctl replace personalCert --boilerplate + You can identify the entitlement required by running: verifyctl replace personalCert --entitlements`)) + + personalCertExamples = templates.Examples(cmdutil.TranslateExamples(personalCertMessagePrefix, ` + # Generate an empty personalCert resource template + verifyctl replace personalCert --boilerplate + # Update a personal certificate from a JSON file + verifyctl replace personalCert -f=./personal_cert.json + `)) +) + +type personalCertOptions struct { + options + config *config.CLIConfig +} + +func NewPersonalCertCommand(config *config.CLIConfig, streams io.ReadWriter) *cobra.Command { + o := &personalCertOptions{ + config: config, + } + cmd := &cobra.Command{ + Use: personalCertUsage, + Short: personalCertShortDesc, + Long: personalCertLongDesc, + Example: personalCertExamples, + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.ExitOnError(cmd, o.Complete(cmd, args)) + cmdutil.ExitOnError(cmd, o.Validate(cmd, args)) + cmdutil.ExitOnError(cmd, o.Run(cmd, args)) + }, + } + cmd.SetOut(streams) + cmd.SetErr(streams) + cmd.SetIn(streams) + o.AddFlags(cmd) + return cmd +} + +func (o *personalCertOptions) AddFlags(cmd *cobra.Command) { + o.addCommonFlags(cmd, personalCertResourceName) + cmd.Flags().StringVarP(&o.file, "file", "f", "", i18n.Translate("Path to the file that contains the input data. The contents of the file are expected to be formatted to match the API contract.")) +} + +func (o *personalCertOptions) Complete(cmd *cobra.Command, args []string) error { + return nil +} + +func (o *personalCertOptions) Validate(cmd *cobra.Command, args []string) error { + if o.entitlements || o.boilerplate { + return nil + } + if len(o.file) == 0 { + return errorsx.G11NError(i18n.Translate("'file' option is required if no other options are used.")) + } + return nil +} + +func (o *personalCertOptions) Run(cmd *cobra.Command, args []string) error { + if o.entitlements { + cmdutil.WriteString(cmd, entitlementsMessage+" "+personalCertEntitlements) + return nil + } + + if o.boilerplate { + resourceObj := &resource.ResourceObject{ + Kind: resource.ResourceTypePrefix + "PersonalCert", + APIVersion: "1.0", + Data: &security.PersonalCert{ + Label: "