From 0696095bb0acb860089efb74146b490fc07fa477 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Mon, 1 Jun 2026 13:20:06 +0200 Subject: [PATCH] add roles ls+sh --- README.md | 3 + cmd/roles.go | 78 +++++++++++++++++ cmd/root.go | 1 + pkg/es/role.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 cmd/roles.go create mode 100644 pkg/es/role.go diff --git a/README.md b/README.md index 6e990e5..4740013 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ Command tree: list show repl + role + list + show search shard list diff --git a/cmd/roles.go b/cmd/roles.go new file mode 100644 index 0000000..89538a9 --- /dev/null +++ b/cmd/roles.go @@ -0,0 +1,78 @@ +/* +Copyright © 2026 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package cmd + +import ( + "context" + "errors" + + "codeberg.org/scip/esctl/pkg/cfg" + "codeberg.org/scip/esctl/pkg/es" + + "github.com/urfave/cli/v3" +) + +func Roles(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "role", + Usage: "manage roles", + + Commands: []*cli.Command{ + RoleList(conf), + RoleShow(conf), + }, + } +} + +func RoleList(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "list", + Aliases: []string{"ls"}, + Usage: "list roles", + + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "orphaned", + Usage: "include only orphaned roles", + Destination: &conf.Failed, + Aliases: []string{"o"}, + }, + }, + + Action: func(ctx context.Context, cmd *cli.Command) error { + return es.RoleList(conf) + }, + } +} + +func RoleShow(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "show", + Aliases: []string{"sh"}, + Usage: "show details about a role", + UsageText: "show [options] ", + + Action: func(ctx context.Context, cmd *cli.Command) error { + index := cmd.Args().Get(0) + if index == "" { + return errors.New("no role specified") + } + + return es.RoleShow(conf, cmd.Args().Get(0)) + }, + } +} diff --git a/cmd/root.go b/cmd/root.go index f7d7bea..0ec31d7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -97,6 +97,7 @@ func Main() int { Repl(conf), Version(conf), Debug(conf), + Roles(conf), }, Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) { diff --git a/pkg/es/role.go b/pkg/es/role.go new file mode 100644 index 0000000..6284600 --- /dev/null +++ b/pkg/es/role.go @@ -0,0 +1,232 @@ +/* +Copyright © 2026 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package es + +import ( + "context" + "fmt" + "log/slog" + "strings" + + "codeberg.org/scip/esctl/pkg/cfg" + "codeberg.org/scip/esctl/pkg/printer" + "github.com/elastic/go-elasticsearch/v9/typedapi/types" +) + +func RoleList(conf *cfg.Config) error { + res, err := conf.DefaultCluster.ES.Security.GetRole(). + Do(context.Background()) + if err != nil { + return fmt.Errorf("failed to get roles: %s", esErrorString(err)) + } + + slog.Debug("ES result", "roles", res) + + table := printer.NewTable(conf, 3, len(res)) + table.Addheaders("role", "index roles", "cluster roles") + + idx := 0 + for name, role := range res { + table.Entries[idx] = []string{ + name, + fmt.Sprintf("%d", len(role.Cluster)), + fmt.Sprintf("%d", len(role.Indices)), + } + idx++ + } + + table.Sort() + if err := table.Print(); err != nil { + return err + } + + return nil +} + +func RoleShow(conf *cfg.Config, rolename string) error { + res, err := conf.DefaultCluster.ES.Security.GetRole(). + Name(rolename). + Do(context.Background()) + if err != nil { + return fmt.Errorf("failed to get role: %s", esErrorString(err)) + } + + slog.Debug("ES result", "role", res) + + role, exists := res[rolename] + if !exists { + return fmt.Errorf("role %s does not exist", rolename) + } + + if len(role.Indices) > 0 { + if err := roleIndices(conf, role); err != nil { + return err + } + + fmt.Println() + } + + if len(role.RemoteIndices) > 0 { + if err := roleRemoteIndices(conf, role); err != nil { + return err + } + + fmt.Println() + } + + if len(role.Cluster) > 0 { + if err := roleClusters(conf, role); err != nil { + return err + } + + fmt.Println() + } + + if len(role.RemoteCluster) > 0 { + if err := roleRemoteClusters(conf, role); err != nil { + return err + } + + fmt.Println() + } + + if len(role.Applications) > 0 { + if err := roleApplications(conf, role); err != nil { + return err + } + } + + return nil +} + +func roleRemoteClusters(conf *cfg.Config, role types.Role) error { + if len(role.RemoteCluster) == 0 { + return nil + } + + table := printer.NewTable(conf, 2, len(role.RemoteCluster)) + table.Addheaders("remote cluster", "privilege") + idx := 0 + for _, priv := range role.RemoteCluster { + perms := []string{} + for _, perm := range priv.Privileges { + perms = append(perms, perm.Name) + } + + table.Entries[idx] = []string{ + strings.Join(priv.Clusters, ","), + strings.Join(perms, ","), + } + idx++ + } + + table.Sort() + return table.Print() +} + +func roleClusters(conf *cfg.Config, role types.Role) error { + if len(role.Cluster) == 0 { + return nil + } + + table := printer.NewTable(conf, 1, len(role.Cluster)) + table.Addheaders("cluster rights") + idx := 0 + for _, cluster := range role.Cluster { + table.Entries[idx] = []string{cluster.Name} + idx++ + } + + table.Sort() + return table.Print() +} + +func roleRemoteIndices(conf *cfg.Config, role types.Role) error { + if len(role.RemoteIndices) == 0 { + return nil + } + + table := printer.NewTable(conf, 3, len(role.RemoteIndices)) + table.Addheaders("remote index names", "index permissions", "allow restricted") + idx := 0 + for _, priv := range role.RemoteIndices { + perms := []string{} + for _, perm := range priv.Privileges { + perms = append(perms, perm.Name) + } + + table.Entries[idx] = []string{ + strings.Join(priv.Names, ", "), + strings.Join(perms, ", "), + fmt.Sprintf("%t", *priv.AllowRestrictedIndices), + } + + idx++ + } + + table.Sort() + return table.Print() +} + +func roleIndices(conf *cfg.Config, role types.Role) error { + if len(role.Indices) == 0 { + return nil + } + + table := printer.NewTable(conf, 3, len(role.Indices)) + table.Addheaders("index names", "index permissions", "allow restricted") + idx := 0 + for _, priv := range role.Indices { + perms := []string{} + for _, perm := range priv.Privileges { + perms = append(perms, perm.Name) + } + + table.Entries[idx] = []string{ + strings.Join(priv.Names, ", "), + strings.Join(perms, ", "), + fmt.Sprintf("%t", *priv.AllowRestrictedIndices), + } + + idx++ + } + + table.Sort() + return table.Print() +} + +func roleApplications(conf *cfg.Config, role types.Role) error { + if len(role.Applications) == 0 { + return nil + } + + table := printer.NewTable(conf, 3, len(role.Applications)) + table.Addheaders("application", "privileges", "resources") + idx := 0 + for _, priv := range role.Applications { + table.Entries[idx] = []string{ + priv.Application, + strings.Join(priv.Privileges, ", "), + strings.Join(priv.Resources, ", "), + } + + idx++ + } + + table.Sort() + return table.Print() +}