Skip to content

Commit

Permalink
cli: add more rbac utils sub-commands
Browse files Browse the repository at this point in the history
* scan-user-permissions
* cleanup-user-permissions
  • Loading branch information
mozillazg committed Nov 24, 2023
1 parent 7ea5187 commit debdeb1
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 30 deletions.
50 changes: 50 additions & 0 deletions pkg/ctl/rbac/README.zh-cn.md
@@ -0,0 +1,50 @@
# RBAC


## scan-user-permissions

扫描指定集群中存在的 RAM 用户和角色的 RBAC bindings.

```shell

# 默认只输出已删除 RAM 用户和角色
$ ack-ram-tool rbac scan-user-permissions -c <集群ID>
UID UserType UserName Binding
2432******** (deleted) RamUser ClusterRoleBinding/-/24*****-clusterrolebinding


# 可以通过 -A 显示所有用户和角色
$ ack-ram-tool rbac scan-user-permissions -A -c <集群ID>
UID UserType UserName Binding
2432******** (deleted) RamUser ClusterRoleBinding/-/24*****-clusterrolebinding
2342******** RamUser foobar ClusterRoleBinding/-/23*****-clusterrolebinding

```

## cleanup-user-permissions

清理指定集群中指定的 RAM 用户和角色的 RBAC bindings.

```shell

# <用户ID> 可以从上面的 scan 命令的结果中获取
$ ack-ram-tool rbac cleanup-user-permissions -c <集群ID> -u <用户ID>
Start to scan users and bindings
Will cleanup RBAC bindings as blow:
UID UserType UserName Binding
300******************** (deleted) RamRole RoleBinding/kube-system/300********************-heapster-rolebinding
300******************** (deleted) RamRole RoleBinding/arms-prom/300********************-arms-prom-rolebinding
300******************** (deleted) RamRole RoleBinding/default/300********************-default-rolebinding
300******************** (deleted) RamRole ClusterRoleBinding/-/300********************-clusterrolebinding
? Are you sure you want to cleanup these bindings? Yes
start to cleanup binding: RoleBinding/kube-system/300********************-heapster-rolebinding
finished cleanup binding: RoleBinding/kube-system/300********************-heapster-rolebinding
start to cleanup binding: RoleBinding/arms-prom/300********************-arms-prom-rolebinding
finished cleanup binding: RoleBinding/arms-prom/300********************-arms-prom-rolebinding
start to cleanup binding: RoleBinding/default/300********************-default-rolebinding
finished cleanup binding: RoleBinding/default/300********************-default-rolebinding
start to cleanup binding: ClusterRoleBinding/-/300********************-clusterrolebinding
finished cleanup binding: ClusterRoleBinding/-/300********************-clusterrolebinding
all bindings have been cleanup

```
@@ -1,8 +1,9 @@
package scanuserpermissions
package binding

import (
"context"
"errors"
"fmt"
"regexp"
"sort"
"strconv"
Expand All @@ -13,11 +14,11 @@ import (
"k8s.io/client-go/kubernetes"
)

type BindingKind string
type Kind string

var (
BindingKindRoleBinding BindingKind = "RoleBinding"
BindingKindClusterRoleBinding BindingKind = "ClusterRoleBinding"
KindRoleBinding Kind = "RoleBinding"
KindClusterRoleBinding Kind = "ClusterRoleBinding"
)

type RawBindings struct {
Expand All @@ -26,6 +27,7 @@ type RawBindings struct {
}

var errInvalidName = errors.New("invalid name")
var regexAliUserIdentity = regexp.MustCompile(`^(\d+)(-\d+)?$`)

func (bs *RawBindings) AliUserBindings() RawBindings {
filtered := RawBindings{}
Expand All @@ -43,19 +45,21 @@ func (bs *RawBindings) AliUserBindings() RawBindings {
}

type Binding struct {
Kind BindingKind
Kind Kind
Name string
Namespace string
SubjectName string
AliUid int
AliUid int64
}

func (bs *RawBindings) SortByUid() []Binding {
var bindList []Binding
for _, b := range bs.RoleBindings {
for _, sub := range b.Subjects {
bindList = append(bindList, Binding{
Kind: BindingKindRoleBinding,
Kind: KindRoleBinding,
Name: b.Name,
Namespace: b.Namespace,
SubjectName: sub.Name,
AliUid: 0,
})
Expand All @@ -64,8 +68,9 @@ func (bs *RawBindings) SortByUid() []Binding {
for _, b := range bs.ClusterRoleBindings {
for _, sub := range b.Subjects {
bindList = append(bindList, Binding{
Kind: BindingKindClusterRoleBinding,
Kind: KindClusterRoleBinding,
Name: b.Name,
Namespace: b.Namespace,
SubjectName: sub.Name,
AliUid: 0,
})
Expand All @@ -86,18 +91,18 @@ func (bs *RawBindings) SortByUid() []Binding {
return bindList
}

func listBindings(ctx context.Context, kube kubernetes.Interface) (*RawBindings, error) {
rolebindings, err := listRoleBindings(ctx, kube)
func ListBindings(ctx context.Context, kube kubernetes.Interface) (*RawBindings, error) {
roleBindings, err := listRoleBindings(ctx, kube)
if err != nil {
return nil, err
}
clusterroleBindings, err := listClusterRoleBindings(ctx, kube)
clusterRoleBindings, err := listClusterRoleBindings(ctx, kube)
if err != nil {
return nil, err
}
return &RawBindings{
RoleBindings: rolebindings.Items,
ClusterRoleBindings: clusterroleBindings.Items,
RoleBindings: roleBindings.Items,
ClusterRoleBindings: clusterRoleBindings.Items,
}, nil
}

Expand All @@ -119,7 +124,7 @@ func isAliUserClusterRoleBinding(binding rbacv1.ClusterRoleBinding) bool {
return false
}

func getAliUidFromSubjectName(name string) (int, error) {
func getAliUidFromSubjectName(name string) (int64, error) {
matches := regexAliUserIdentity.FindAllStringSubmatch(name, -1)
if len(matches) < 1 {
return 0, errInvalidName
Expand All @@ -132,11 +137,9 @@ func getAliUidFromSubjectName(name string) (int, error) {
if err != nil {
return 0, err
}
return uid, nil
return int64(uid), nil
}

var regexAliUserIdentity = regexp.MustCompile(`^(\d+)(-\d+)?$`)

func isAliUserSubject(subject rbacv1.Subject) bool {
if subject.Kind != rbacv1.UserKind {
return false
Expand Down Expand Up @@ -205,3 +208,33 @@ func listClusterRoleBindings(ctx context.Context, kube kubernetes.Interface) (*r

return allList, nil
}

func RemoveBinding(ctx context.Context, b Binding, client kubernetes.Interface) error {
switch b.Kind {
case KindRoleBinding:
return removeRoleBinding(ctx, b, client)
case KindClusterRoleBinding:
return removeClusterRoleBinding(ctx, b, client)
}
return nil
}

func removeClusterRoleBinding(ctx context.Context, b Binding, client kubernetes.Interface) error {
err := client.RbacV1().ClusterRoleBindings().Delete(ctx, b.Name, metav1.DeleteOptions{})
return err
}

func removeRoleBinding(ctx context.Context, b Binding, client kubernetes.Interface) error {
err := client.RbacV1().RoleBindings(b.Namespace).Delete(ctx, b.Name, metav1.DeleteOptions{})
return err
}

func (b Binding) String() string {
switch b.Kind {
case KindRoleBinding:
return fmt.Sprintf("%s/%s/%s", b.Kind, b.Namespace, b.Name)
case KindClusterRoleBinding:
return fmt.Sprintf("%s/-/%s", b.Kind, b.Name)
}
return fmt.Sprintf("%v", b)

Check failure on line 239 in pkg/ctl/rbac/binding/binding.go

View workflow job for this annotation

GitHub Actions / build

fmt.Sprintf format %v with arg b causes recursive (github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac/binding.Binding).String method call

Check failure on line 239 in pkg/ctl/rbac/binding/binding.go

View workflow job for this annotation

GitHub Actions / lint

printf: fmt.Sprintf format %v with arg b causes recursive (github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac/binding.Binding).String method call (govet)
}
@@ -1,4 +1,4 @@
package scanuserpermissions
package binding

import (
"testing"
Expand All @@ -11,7 +11,7 @@ func Test_getAliUidFromSubjectName(t *testing.T) {
tests := []struct {
name string
args args
want int
want int64
wantErr bool
}{
{
Expand Down
39 changes: 39 additions & 0 deletions pkg/ctl/rbac/binding/ram.go
@@ -0,0 +1,39 @@
package binding

import (
"context"
"github.com/AliyunContainerService/ack-ram-tool/pkg/log"
"github.com/AliyunContainerService/ack-ram-tool/pkg/openapi"
"github.com/AliyunContainerService/ack-ram-tool/pkg/types"
"strconv"
)

func ListAccounts(ctx context.Context, client openapi.RamClientInterface) (map[int64]types.Account, error) {
accounts := make(map[int64]types.Account, 0)
users, err := client.ListUsers(ctx)
if err != nil {
log.Logger.Errorf("list users failed: %s", err)
return nil, err
}
roles, err := client.ListRoles(ctx)
if err != nil {
log.Logger.Errorf("list roles failed: %s", err)
return nil, err
}
for _, u := range users {
id, _ := strconv.ParseInt(u.Id, 10, 64)
accounts[id] = types.Account{
Type: types.AccountTypeUser,
User: u,
}
}
for _, r := range roles {
id, _ := strconv.ParseInt(r.RoleId, 10, 64)
accounts[id] = types.Account{
Type: types.AccountTypeRole,
Role: r,
}
}

return accounts, nil
}
116 changes: 116 additions & 0 deletions pkg/ctl/rbac/cleanupuserpermissions/cmd.go
@@ -0,0 +1,116 @@
package cleanupuserpermissions

import (
"context"
"github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac/scanuserpermissions"
"github.com/AliyunContainerService/ack-ram-tool/pkg/log"
"github.com/briandowns/spinner"
"time"

ctlcommon "github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/common"
"github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac/binding"
"github.com/AliyunContainerService/ack-ram-tool/pkg/openapi"
"github.com/AliyunContainerService/ack-ram-tool/pkg/types"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
)

type Option struct {
userId int64

clusterId string
privateIpAddress bool
temporaryDuration time.Duration
//outputFormat OutputFormat
allUsers bool
}

var opts = Option{
temporaryDuration: time.Hour,
}

var cmd = &cobra.Command{
Use: "cleanup-user-permissions",
Short: "cleanup RBAC permissions for one user",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
run()
},
}

func run() {
ctx := context.Background()
openAPIClient := ctlcommon.GetClientOrDie()

oneCluster(ctx, openAPIClient, opts.clusterId)
}

func oneCluster(ctx context.Context, openAPIClient openapi.ClientInterface, clusterId string) {
kubeClient := getKubeClient(ctx, openAPIClient, clusterId)

log.Logger.Info("Start to scan users and bindings")
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
spin.Start()
defer spin.Stop()

rawBindings, err := binding.ListBindings(ctx, kubeClient)
ctlcommon.ExitIfError(err)
accounts, err := binding.ListAccounts(ctx, openAPIClient)
ctlcommon.ExitIfError(err)
spin.Stop()

bindings := rawBindings.SortByUid()
cleanup(ctx, bindings, accounts, kubeClient)
}

func cleanup(ctx context.Context, bindings []binding.Binding,
accounts map[int64]types.Account, kube kubernetes.Interface) {
var newBindings []binding.Binding
for _, b := range bindings {
if b.AliUid == 0 {
continue
}
if opts.userId != 0 && b.AliUid != opts.userId {
continue
}
acc, ok := accounts[b.AliUid]
if !ok {
acc = types.NewFakeAccount(b.AliUid)
acc.MarkDeleted()
accounts[b.AliUid] = acc
}
newBindings = append(newBindings, b)
}

log.Logger.Info("Will cleanup RBAC bindings as blow:")
scanuserpermissions.OutputBindingsTable(newBindings, accounts, false)

ctlcommon.YesOrExit("Are you sure you want to cleanup these bindings?")
for _, b := range newBindings {
log.Logger.Infof("start to cleanup binding: %s", b.String())
if err := binding.RemoveBinding(ctx, b, kube); err != nil {
ctlcommon.ExitIfError(err)
}
log.Logger.Infof("finished cleanup binding: %s", b.String())
}
log.Logger.Info("all bindings have been cleanup")
}

func getKubeClient(ctx context.Context, openAPIClient openapi.ClientInterface, clusterId string) kubernetes.Interface {
kubeconfig, err := openAPIClient.GetUserKubeConfig(ctx, clusterId,
opts.privateIpAddress, opts.temporaryDuration)
ctlcommon.ExitIfError(err)

client, err := ctlcommon.NewKubeClient(kubeconfig.RawData)
ctlcommon.ExitIfError(err)
return client
}

func SetupCmd(rootCmd *cobra.Command) {
rootCmd.AddCommand(cmd)
cmd.Flags().Int64VarP(&opts.userId, "user-id", "u", 0, "limit user id")
cmd.Flags().StringVarP(&opts.clusterId, "cluster-id", "c", "", "cluster id")
//cmd.Flags().BoolVarP(&opts.allUsers, "all-users", "A", false, "list all users")
ctlcommon.ExitIfError(cmd.MarkFlagRequired("cluster-id"))
ctlcommon.ExitIfError(cmd.MarkFlagRequired("user-id"))
}
2 changes: 2 additions & 0 deletions pkg/ctl/rbac/root.go
@@ -1,6 +1,7 @@
package rbac

import (
"github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac/cleanupuserpermissions"
"github.com/AliyunContainerService/ack-ram-tool/pkg/ctl/rbac/scanuserpermissions"
"github.com/spf13/cobra"
)
Expand All @@ -13,6 +14,7 @@ var rootCmd = &cobra.Command{

func init() {
scanuserpermissions.SetupCmd(rootCmd)
cleanupuserpermissions.SetupCmd(rootCmd)
}

func SetupCmd(root *cobra.Command) {
Expand Down

0 comments on commit debdeb1

Please sign in to comment.