diff --git a/docs/commands/rhoas_kafka_acl.adoc b/docs/commands/rhoas_kafka_acl.adoc index 49ad719fd1..fc2ccfbdda 100644 --- a/docs/commands/rhoas_kafka_acl.adoc +++ b/docs/commands/rhoas_kafka_acl.adoc @@ -17,14 +17,17 @@ By default, every users and service account have limited access to their Kafka i .... # Grant access to principal for consuming messages from all topics -$ rhoas kafka acl grant-access --consumer --user user_name --topic all --group all +$ rhoas kafka acl grant-access --consumer --user foo_user --topic all --group all # Grant access to principal for producing messages to all topics -$ rhoas kafka acl grant-access --producer --user user_name --topic all +$ rhoas kafka acl grant-access --producer --user foo_user --topic all # List ACL rules for a Kafka instance $ rhoas kafka acl list +# Give admin rights to user "abc" +$ rhoas kafka acl grant-admin --user abc + .... [discrete] @@ -58,6 +61,13 @@ ifdef::pantheonenv[] * link:{path}#ref-rhoas-kafka-acl-grant-access_{context}[rhoas kafka acl grant-access] - Add ACL rules to grant users access to produce/consume from topics endif::[] +ifdef::env-github,env-browser[] +* link:rhoas_kafka_acl_grant-admin.adoc#rhoas-kafka-acl-grant-admin[rhoas kafka acl grant-admin] - Grant an account permissions to create and delete ACLs in the Kafka instance +endif::[] +ifdef::pantheonenv[] +* link:{path}#ref-rhoas-kafka-acl-grant-admin_{context}[rhoas kafka acl grant-admin] - Grant an account permissions to create and delete ACLs in the Kafka instance +endif::[] + ifdef::env-github,env-browser[] * link:rhoas_kafka_acl_list.adoc#rhoas-kafka-acl-list[rhoas kafka acl list] - List all Kafka ACL rules. endif::[] diff --git a/docs/commands/rhoas_kafka_acl_grant-access.adoc b/docs/commands/rhoas_kafka_acl_grant-access.adoc index 431f384b95..c00dae17e8 100644 --- a/docs/commands/rhoas_kafka_acl_grant-access.adoc +++ b/docs/commands/rhoas_kafka_acl_grant-access.adoc @@ -34,10 +34,10 @@ $ rhoas kafka acl grant-access --consumer --user user_name --topic-prefix "abc" $ rhoas kafka acl grant-access --producer --user user_name --topic-prefix "abc" # Grant access to all users for consuming messages from topic "my-topic" -$ rhoas kafka acl grant-access --consumer --user all --topic my-topic --group my-group +$ rhoas kafka acl grant-access --consumer --all-accounts --topic my-topic --group my-group # Grant access to all users for producing messages to topic "my-topic" -$ rhoas kafka acl grant-access --producer --user all --topic my-topic +$ rhoas kafka acl grant-access --producer --all-accounts --topic my-topic # Grant access to principal for produce and consume messages from all topics $ rhoas kafka acl grant-access --producer --consumer --user user_name --topic all --group all diff --git a/docs/commands/rhoas_kafka_acl_grant-admin.adoc b/docs/commands/rhoas_kafka_acl_grant-admin.adoc new file mode 100644 index 0000000000..22a90c5fbd --- /dev/null +++ b/docs/commands/rhoas_kafka_acl_grant-admin.adoc @@ -0,0 +1,56 @@ +ifdef::env-github,env-browser[:context: cmd] +[id='ref-rhoas-kafka-acl-grant-admin_{context}'] += rhoas kafka acl grant-admin + +[role="_abstract"] +Grant an account permissions to create and delete ACLs in the Kafka instance + +[discrete] +== Synopsis + +This command grants a specified account permission to create and delete ACLs in a Kafka instance. + +.... +rhoas kafka acl grant-admin [flags] +.... + +[discrete] +== Examples + +.... +# Give admin rights to user "abc" +$ rhoas kafka acl grant-admin --user abc + +# Give admin rights to a service account +$ rhoas kafka acl grant-admin --service-account srvc-acct-0837725a-4e69-44e1-af3b-29da30aa85ce + +# Give admin rights to all accounts for a specific kafka instance +$ rhoas kafka acl grant-admin --all-accounts --instance-id c5hv7iru4an1g84pogp0 + +.... + +[discrete] +== Options + + `--all-accounts`:: Set the ACL principal to match all accounts (wildcard). Cannot be used in conjunction with --service-account or --user + `--service-account` _string_:: Service account client ID to use as principal for this operation + `--user` _string_:: User ID to be use as principal + `-y`, `--yes`:: Skip confirmation of this action + +[discrete] +== Options inherited from parent commands + + `-h`, `--help`:: Show help for a command + `-v`, `--verbose`:: Enable verbose mode + +[discrete] +== See also + + +ifdef::env-github,env-browser[] +* link:rhoas_kafka_acl.adoc#rhoas-kafka-acl[rhoas kafka acl] - Kafka ACL management for users and service accounts +endif::[] +ifdef::pantheonenv[] +* link:{path}#ref-rhoas-kafka-acl_{context}[rhoas kafka acl] - Kafka ACL management for users and service accounts +endif::[] + diff --git a/pkg/cmd/kafka/acl/acl.go b/pkg/cmd/kafka/acl/acl.go index 68b68bb67f..1993fa1515 100644 --- a/pkg/cmd/kafka/acl/acl.go +++ b/pkg/cmd/kafka/acl/acl.go @@ -2,6 +2,7 @@ package acl import ( "github.com/redhat-developer/app-services-cli/pkg/cmd/factory" + "github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl/admin" "github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl/delete" "github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl/grant" "github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl/list" @@ -22,6 +23,7 @@ func NewAclCommand(f *factory.Factory) *cobra.Command { list.NewListACLCommand(f), grant.NewGrantPermissionsACLCommand(f), delete.NewDeleteCommand(f), + admin.NewAdminACLCommand(f), ) return cmd diff --git a/pkg/cmd/kafka/acl/admin/admin.go b/pkg/cmd/kafka/acl/admin/admin.go new file mode 100644 index 0000000000..152cf137a7 --- /dev/null +++ b/pkg/cmd/kafka/acl/admin/admin.go @@ -0,0 +1,175 @@ +package admin + +import ( + "context" + + "github.com/AlecAivazis/survey/v2" + "github.com/redhat-developer/app-services-cli/internal/config" + "github.com/redhat-developer/app-services-cli/pkg/cmd/factory" + "github.com/redhat-developer/app-services-cli/pkg/connection" + "github.com/redhat-developer/app-services-cli/pkg/dump" + "github.com/redhat-developer/app-services-cli/pkg/icon" + "github.com/redhat-developer/app-services-cli/pkg/iostreams" + "github.com/redhat-developer/app-services-cli/pkg/kafka/aclutil" + "github.com/redhat-developer/app-services-cli/pkg/localize" + "github.com/redhat-developer/app-services-cli/pkg/logging" + "github.com/spf13/cobra" + + flagset "github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl/flagutil" + + kafkainstanceclient "github.com/redhat-developer/app-services-sdk-go/kafkainstance/apiv1internal/client" +) + +var ( + serviceAccount string + userID string + allAccounts bool +) + +type options struct { + config config.IConfig + connection factory.ConnectionFunc + logger logging.Logger + io *iostreams.IOStreams + localizer localize.Localizer + context context.Context + + kafkaID string + principal string + skipConfirm bool +} + +// NewAdminACLCommand creates ACL rule to aloow user to add and delete ACL rules +func NewAdminACLCommand(f *factory.Factory) *cobra.Command { + + opts := &options{ + config: f.Config, + connection: f.Connection, + logger: f.Logger, + io: f.IOStreams, + localizer: f.Localizer, + context: f.Context, + } + + cmd := &cobra.Command{ + Use: "grant-admin", + Short: f.Localizer.MustLocalize("kafka.acl.grantAdmin.cmd.shortDescription"), + Long: f.Localizer.MustLocalize("kafka.acl.grantAdmin.cmd.longDescription"), + Example: f.Localizer.MustLocalize("kafka.acl.grantAdmin.cmd.example"), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + + cfg, err := opts.config.Load() + if err != nil { + return err + } + + instanceID, ok := cfg.GetKafkaIdOk() + + if !ok { + return opts.localizer.MustLocalizeError("kafka.acl.common.error.noKafkaSelected") + } + + opts.kafkaID = instanceID + + // check if principal is provided + if userID == "" && serviceAccount == "" && !allAccounts { + return opts.localizer.MustLocalizeError("kafka.acl.common.error.noPrincipalsSelected") + } + + // user and service account can't be along with "--all-accounts" flag + if allAccounts && (serviceAccount != "" || userID != "") { + return opts.localizer.MustLocalizeError("kafka.acl.common.error.allAccountsCannotBeUsedWithUserFlag") + } + + // user and service account should not allow wildcard + if userID == aclutil.Wildcard || serviceAccount == aclutil.Wildcard { + return opts.localizer.MustLocalizeError("kafka.acl.common.error.useAllAccountsFlag") + } + + if userID != "" { + opts.principal = userID + } + + if serviceAccount != "" { + opts.principal = serviceAccount + } + + if allAccounts { + opts.principal = aclutil.Wildcard + } + + return runAdmin(opts) + }, + } + + fs := flagset.NewFlagSet(cmd, opts.localizer, opts.connection) + + fs.AddUser(&userID) + fs.AddServiceAccount(&serviceAccount) + fs.AddAllAccounts(&allAccounts) + fs.AddYes(&opts.skipConfirm) + + return cmd +} + +func runAdmin(opts *options) (err error) { + + conn, err := opts.connection(connection.DefaultConfigRequireMasAuth) + if err != nil { + return err + } + + api, kafkaInstance, err := conn.API().KafkaAdmin(opts.kafkaID) + if err != nil { + return err + } + + kafkaName := kafkaInstance.GetName() + + req := api.AclsApi.CreateAcl(opts.context) + + aclBindClusterAlter := kafkainstanceclient.NewAclBinding( + kafkainstanceclient.ACLRESOURCETYPE_CLUSTER, + aclutil.KafkaCluster, + kafkainstanceclient.ACLPATTERNTYPE_LITERAL, + aclutil.FormatPrincipal(opts.principal), + kafkainstanceclient.ACLOPERATION_ALTER, + kafkainstanceclient.ACLPERMISSIONTYPE_ALLOW, + ) + + opts.logger.Info(opts.localizer.MustLocalize("kafka.acl.grantPermissions.log.info.aclsPreview")) + opts.logger.Info() + + rows := aclutil.MapACLsToTableRows([]kafkainstanceclient.AclBinding{*aclBindClusterAlter}, opts.localizer) + dump.Table(opts.io.Out, rows) + opts.logger.Info() + + if !opts.skipConfirm { + var confirmGrant bool + promptConfirmGrant := &survey.Confirm{ + Message: opts.localizer.MustLocalize("kafka.acl.common.input.confirmGrant.message"), + } + + err = survey.AskOne(promptConfirmGrant, &confirmGrant) + if err != nil { + return err + } + + if !confirmGrant { + opts.logger.Debug(opts.localizer.MustLocalize("kafka.acl.grantAdmin.log.debug.grantNotConfirmed")) + return nil + } + } + + req = req.AclBinding(*aclBindClusterAlter) + + err = aclutil.ExecuteACLRuleCreate(req, opts.localizer, kafkaName) + if err != nil { + return err + } + + opts.logger.Info(icon.SuccessPrefix(), opts.localizer.MustLocalize("kafka.acl.grantAdmin.log.info.successful", localize.NewEntry("Account", opts.principal), localize.NewEntry("InstanceName", kafkaName))) + + return nil +} diff --git a/pkg/cmd/kafka/acl/delete/delete.go b/pkg/cmd/kafka/acl/delete/delete.go index 03bfe017ae..f31ba96f0b 100644 --- a/pkg/cmd/kafka/acl/delete/delete.go +++ b/pkg/cmd/kafka/acl/delete/delete.go @@ -286,7 +286,7 @@ func validateAndSetOpts(opts *options) error { opts.principal = aclutil.Wildcard } - // check if priincipal is provided + // check if principal is provided if !allAccounts && (userID == "" && serviceAccount == "") { return opts.localizer.MustLocalizeError("kafka.acl.common.error.noPrincipalsSelected") } diff --git a/pkg/cmd/kafka/acl/grant/grant.go b/pkg/cmd/kafka/acl/grant/grant.go index a0887a1472..80f37466f6 100644 --- a/pkg/cmd/kafka/acl/grant/grant.go +++ b/pkg/cmd/kafka/acl/grant/grant.go @@ -270,18 +270,18 @@ func runGrantPermissions(opts *options) (err error) { opts.Logger.Info() if !opts.force { - var confirmDelete bool - promptConfirmDelete := &survey.Confirm{ - Message: opts.localizer.MustLocalize("kafka.acl.grantPermissions.input.confirmGrant.message"), + var confirmGrant bool + promptConfirmGrant := &survey.Confirm{ + Message: opts.localizer.MustLocalize("kafka.acl.common.input.confirmGrant.message"), } - err = survey.AskOne(promptConfirmDelete, &confirmDelete) + err = survey.AskOne(promptConfirmGrant, &confirmGrant) if err != nil { return err } - if !confirmDelete { - opts.Logger.Debug(opts.localizer.MustLocalize("kafka.acl.grantPermissions.log.debug.deleteNotConfirmed")) + if !confirmGrant { + opts.Logger.Debug(opts.localizer.MustLocalize("kafka.acl.grantPermissions.log.debug.grantNotConfirmed")) return nil } } @@ -306,7 +306,7 @@ func validateFlagInputCombination(opts *options) error { return opts.localizer.MustLocalizeError("kafka.acl.common.error.noOperationSpecified") } - // check if priincipal is provided + // check if principal is provided if userID == "" && serviceAccount == "" && !allAccounts { return opts.localizer.MustLocalizeError("kafka.acl.common.error.noPrincipalsSelected") } @@ -350,5 +350,10 @@ func validateFlagInputCombination(opts *options) error { ) } + // user and service account should not allow wildcard + if userID == aclutil.Wildcard || serviceAccount == aclutil.Wildcard { + return opts.localizer.MustLocalizeError("kafka.acl.common.error.useAllAccountsFlag") + } + return nil } diff --git a/pkg/localize/locales/en/cmd/acl.en.toml b/pkg/localize/locales/en/cmd/acl.en.toml index d5de4638f1..eecb4a8619 100644 --- a/pkg/localize/locales/en/cmd/acl.en.toml +++ b/pkg/localize/locales/en/cmd/acl.en.toml @@ -11,13 +11,16 @@ By default, every users and service account have limited access to their Kafka i [kafka.acl.cmd.example] one = ''' # Grant access to principal for consuming messages from all topics -$ rhoas kafka acl grant-access --consumer --user user_name --topic all --group all +$ rhoas kafka acl grant-access --consumer --user foo_user --topic all --group all # Grant access to principal for producing messages to all topics -$ rhoas kafka acl grant-access --producer --user user_name --topic all +$ rhoas kafka acl grant-access --producer --user foo_user --topic all # List ACL rules for a Kafka instance $ rhoas kafka acl list + +# Give admin rights to user "abc" +$ rhoas kafka acl grant-admin --user abc ''' [kafka.acl.common] @@ -108,6 +111,9 @@ one = 'is' [kafka.acl.common.startsWith] one = 'starts with' +[kafka.acl.common.input.confirmGrant.message] +one = 'Are you sure you want to create the listed ACL rules?' + [kafka.acl.list] [kafka.acl.list.cmd.example] @@ -173,10 +179,10 @@ $ rhoas kafka acl grant-access --consumer --user user_name --topic-prefix "abc" $ rhoas kafka acl grant-access --producer --user user_name --topic-prefix "abc" # Grant access to all users for consuming messages from topic "my-topic" -$ rhoas kafka acl grant-access --consumer --user all --topic my-topic --group my-group +$ rhoas kafka acl grant-access --consumer --all-accounts --topic my-topic --group my-group # Grant access to all users for producing messages to topic "my-topic" -$ rhoas kafka acl grant-access --producer --user all --topic my-topic +$ rhoas kafka acl grant-access --producer --all-accounts --topic my-topic # Grant access to principal for produce and consume messages from all topics $ rhoas kafka acl grant-access --producer --consumer --user user_name --topic all --group all @@ -224,10 +230,7 @@ one = 'The following ACL rules are to be created:' [kafka.acl.grantPermissions.log.info.aclsCreated] one = 'ACLs successfully created in the Kafka instance "{{.InstanceName}}"' -[kafka.acl.grantPermissions.input.confirmGrant.message] -one = 'Are you sure you want to create the listed ACL rules' - -[kafka.acl.grantPermissions.log.debug.deleteNotConfirmed] +[kafka.acl.grantPermissions.log.debug.grantNotConfirmed] one = 'Kafka ACLs grant permission action was not confirmed. Exiting silently' [kafka.acl.delete] @@ -260,4 +263,30 @@ one = 'Deleted {{.Count}} ACL from Kafka instance "{{.Name}}"' other = 'Deleted {{.Count}} ACLs from Kafka instance "{{.Name}}"' [kafka.acl.delete.log.info.deletingACLs] -one = 'Deleting ACLs from Kafka instance "{{.Name}}"' \ No newline at end of file +one = 'Deleting ACLs from Kafka instance "{{.Name}}"' + +[kafka.acl.grantAdmin] + +[kafka.acl.grantAdmin.cmd.shortDescription] +one = 'Grant an account permissions to create and delete ACLs in the Kafka instance' + +[kafka.acl.grantAdmin.cmd.longDescription] +one = 'This command grants a specified account permission to create and delete ACLs in a Kafka instance.' + +[kafka.acl.grantAdmin.cmd.example] +one = ''' +# Give admin rights to user "abc" +$ rhoas kafka acl grant-admin --user abc + +# Give admin rights to a service account +$ rhoas kafka acl grant-admin --service-account srvc-acct-0837725a-4e69-44e1-af3b-29da30aa85ce + +# Give admin rights to all accounts for a specific kafka instance +$ rhoas kafka acl grant-admin --all-accounts --instance-id c5hv7iru4an1g84pogp0 +''' + +[kafka.acl.grantAdmin.log.info.successful] +one = 'Account "{{.Account}}" is now allowed to create and delete ACLs for Kafka instance "{{.InstanceName}}"' + +[kafka.acl.grantAdmin.log.debug.grantNotConfirmed] +one = 'Kafka ACLs grant admin action was not confirmed. Exiting silently' \ No newline at end of file