diff --git a/README.md b/README.md
index aa3145d0..d1efd0e4 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ Kor is a CLI tool to discover unused Kubernetes resources. Currently, Kor can id
- ServiceAccounts
- Deployments
- Statefulsets
+- Roles
![Kor Screenshot](/images/screenshot.png)
@@ -18,13 +19,14 @@ Download the binary for your operating system from the [releases page](https://g
Kor provides various subcommands to identify and list unused resources. The available commands are:
-- `all`: Gets all unused resources (configmaps, secrets, services, and service accounts) for the specified namespace or all namespaces.
+- `all`: Gets all unused resources for the specified namespace or all namespaces.
- `configmap`: Gets unused configmaps for the specified namespace or all namespaces.
- `secret`: Gets unused secrets for the specified namespace or all namespaces.
- `services`: Gets unused services for the specified namespace or all namespaces.
- `serviceaccount`: Gets unused service accounts for the specified namespace or all namespaces.
- `deployments`: Gets unused service accounts for the specified namespace or all namespaces.
- `statefulsets`: Gets unused service accounts for the specified namespace or all namespaces.
+- `role`: Gets unused roles for the specified namespace or all namespaces.
To use a specific subcommand, run `kor [subcommand] [flags]`.
@@ -46,8 +48,10 @@ kor [subcommand] --help
| Secrets | Secrets not used in the following places:
- Pods
- Containers
- Secrets used through volumes
- Secrets used through environment variables
- Secrets used by ingress TLS
-Secrets used by ServiceAccounts | |
| Services | Services with no endpoints | |
| Deployments | Deployments with 0 Replicas | |
-| ServiceAccounts | ServiceAccounts used by pods | |
+| ServiceAccounts | ServiceAccounts unused by pods
ServiceAccounts unused by roleBinding or clusterRoleBinding | |
| Statefulsets | Statefulsets with no endpoints | |
+| Roles | Roles not used in roleBinding | |
+
## Contributing
diff --git a/cmd/kor/roles.go b/cmd/kor/roles.go
new file mode 100644
index 00000000..415970ba
--- /dev/null
+++ b/cmd/kor/roles.go
@@ -0,0 +1,21 @@
+package kor
+
+import (
+ "github.com/spf13/cobra"
+ "github.com/yonahd/kor/pkg/kor"
+)
+
+var roleCmd = &cobra.Command{
+ Use: "role",
+ Short: "Gets unused roles",
+ Args: cobra.NoArgs,
+ Run: func(cmd *cobra.Command, args []string) {
+ kor.GetUnusedRoles(namespace)
+
+ },
+}
+
+func init() {
+ roleCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on")
+ rootCmd.AddCommand(roleCmd)
+}
diff --git a/pkg/kor/all.go b/pkg/kor/all.go
index 60d45b91..a3134507 100644
--- a/pkg/kor/all.go
+++ b/pkg/kor/all.go
@@ -50,7 +50,7 @@ func getUnusedServiceAccounts(kubeClient *kubernetes.Clientset, namespace string
func getUnusedDeployments(kubeClient *kubernetes.Clientset, namespace string) ResourceDiff {
deployDiff, err := ProcessNamespaceDeployments(kubeClient, namespace)
if err != nil {
- fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "serviceaccounts", namespace, err)
+ fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "deployments", namespace, err)
}
namespaceSADiff := ResourceDiff{"Deployment", deployDiff}
return namespaceSADiff
@@ -59,12 +59,21 @@ func getUnusedDeployments(kubeClient *kubernetes.Clientset, namespace string) Re
func getUnusedStatefulsets(kubeClient *kubernetes.Clientset, namespace string) ResourceDiff {
stsDiff, err := ProcessNamespaceStatefulsets(kubeClient, namespace)
if err != nil {
- fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "serviceaccounts", namespace, err)
+ fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "statefulsets", namespace, err)
}
namespaceSADiff := ResourceDiff{"Statefulset", stsDiff}
return namespaceSADiff
}
+func getUnusedRoles(kubeClient *kubernetes.Clientset, namespace string) ResourceDiff {
+ roleDiff, err := processNamespaceRoles(kubeClient, namespace)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "roles", namespace, err)
+ }
+ namespaceSADiff := ResourceDiff{"Role", roleDiff}
+ return namespaceSADiff
+}
+
func GetUnusedAll(namespace string) {
var kubeClient *kubernetes.Clientset
var namespaces []string
@@ -84,8 +93,10 @@ func GetUnusedAll(namespace string) {
allDiffs = append(allDiffs, namespaceSADiff)
namespaceDeploymentDiff := getUnusedDeployments(kubeClient, namespace)
allDiffs = append(allDiffs, namespaceDeploymentDiff)
- namespacestatefulsetDiff := getUnusedStatefulsets(kubeClient, namespace)
- allDiffs = append(allDiffs, namespacestatefulsetDiff)
+ namespaceStatefulsetDiff := getUnusedStatefulsets(kubeClient, namespace)
+ allDiffs = append(allDiffs, namespaceStatefulsetDiff)
+ namespaceRoleDiff := getUnusedRoles(kubeClient, namespace)
+ allDiffs = append(allDiffs, namespaceRoleDiff)
output := FormatOutputAll(namespace, allDiffs)
fmt.Println(output)
fmt.Println()
diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go
new file mode 100644
index 00000000..6c4f2649
--- /dev/null
+++ b/pkg/kor/roles.go
@@ -0,0 +1,103 @@
+package kor
+
+import (
+ "context"
+ "fmt"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+ "os"
+)
+
+func retrieveUsedRoles(clientset *kubernetes.Clientset, namespace string) ([]string, error) {
+ // Get a list of all role bindings in the specified namespace
+ roleBindings, err := clientset.RbacV1().RoleBindings(namespace).List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, fmt.Errorf("failed to list role bindings in namespace %s: %v", namespace, err)
+ }
+
+ // Create a map to store role binding names
+ usedRoles := make(map[string]bool)
+
+ // Populate the map with role binding names
+ for _, rb := range roleBindings.Items {
+ usedRoles[rb.RoleRef.Name] = true
+ }
+
+ // Create a slice to store used role names
+ var usedRoleNames []string
+
+ // Extract used role names from the map
+ for role := range usedRoles {
+ usedRoleNames = append(usedRoleNames, role)
+ }
+
+ return usedRoleNames, nil
+}
+
+func retrieveRoleNames(kubeClient *kubernetes.Clientset, namespace string) ([]string, error) {
+ roles, err := kubeClient.RbacV1().Roles(namespace).List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, err
+ }
+ names := make([]string, 0, len(roles.Items))
+ for _, role := range roles.Items {
+ names = append(names, role.Name)
+ }
+ return names, nil
+}
+
+func calculateRoleDifference(usedRoles []string, roleNames []string) []string {
+ difference := []string{}
+ for _, name := range roleNames {
+ found := false
+ for _, usedName := range usedRoles {
+ if name == usedName {
+ found = true
+ break
+ }
+ }
+ if !found {
+ difference = append(difference, name)
+ }
+ }
+ return difference
+}
+
+func processNamespaceRoles(kubeClient *kubernetes.Clientset, namespace string) ([]string, error) {
+ usedRoles, err := retrieveUsedRoles(kubeClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+
+ usedRoles = RemoveDuplicatesAndSort(usedRoles)
+
+ roleNames, err := retrieveRoleNames(kubeClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+
+ diff := calculateRoleDifference(usedRoles, roleNames)
+ return diff, nil
+
+}
+
+func GetUnusedRoles(namespace string) {
+ var kubeClient *kubernetes.Clientset
+ var namespaces []string
+
+ kubeClient = GetKubeClient()
+
+ namespaces = SetNamespaceList(namespace, kubeClient)
+
+ for _, namespace := range namespaces {
+ diff, err := processNamespaceRoles(kubeClient, namespace)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
+ continue
+ }
+ output := FormatOutput(namespace, diff, "Roles")
+ fmt.Println(output)
+ fmt.Println()
+ }
+}
diff --git a/pkg/kor/serviceaccounts.go b/pkg/kor/serviceaccounts.go
index fa5de81d..d2d1d6a5 100644
--- a/pkg/kor/serviceaccounts.go
+++ b/pkg/kor/serviceaccounts.go
@@ -12,14 +12,58 @@ var exceptionServiceAccounts = []ExceptionResource{
{ResourceName: "default", Namespace: "*"},
}
-func retrieveUsedSA(kubeClient *kubernetes.Clientset, namespace string) ([]string, error) {
+func getServiceAccountsFromClusterRoleBindings(clientset *kubernetes.Clientset, namespace string) ([]string, error) {
+ // Get a list of all role bindings in the specified namespace
+ roleBindings, err := clientset.RbacV1().ClusterRoleBindings().List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, fmt.Errorf("failed to list role bindings in namespace %s: %v", namespace, err)
+ }
+
+ // Create a slice to store service account names
+ var serviceAccounts []string
+
+ // Extract service account names from the role bindings
+ for _, rb := range roleBindings.Items {
+ for _, subject := range rb.Subjects {
+ if subject.Kind == "ServiceAccount" {
+ serviceAccounts = append(serviceAccounts, subject.Name)
+ }
+ }
+ }
+
+ return serviceAccounts, nil
+}
+
+func getServiceAccountsFromRoleBindings(clientset *kubernetes.Clientset, namespace string) ([]string, error) {
+ // Get a list of all role bindings in the specified namespace
+ roleBindings, err := clientset.RbacV1().RoleBindings(namespace).List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, fmt.Errorf("failed to list role bindings in namespace %s: %v", namespace, err)
+ }
+
+ // Create a slice to store service account names
+ var serviceAccounts []string
+
+ // Extract service account names from the role bindings
+ for _, rb := range roleBindings.Items {
+ for _, subject := range rb.Subjects {
+ if subject.Kind == "ServiceAccount" {
+ serviceAccounts = append(serviceAccounts, subject.Name)
+ }
+ }
+ }
+
+ return serviceAccounts, nil
+}
+
+func retrieveUsedSA(kubeClient *kubernetes.Clientset, namespace string) ([]string, []string, []string, error) {
podServiceAccounts := []string{}
// Retrieve pods in the specified namespace
pods, err := kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
// Extract service account names from pods
@@ -35,7 +79,9 @@ func retrieveUsedSA(kubeClient *kubernetes.Clientset, namespace string) ([]strin
}
}
- return podServiceAccounts, nil
+ roleServiceAccounts, err := getServiceAccountsFromRoleBindings(kubeClient, namespace)
+ clusterRoleServiceAccounts, err := getServiceAccountsFromClusterRoleBindings(kubeClient, namespace)
+ return podServiceAccounts, roleServiceAccounts, clusterRoleServiceAccounts, nil
}
func retrieveServiceAccountNames(kubeClient *kubernetes.Clientset, namespace string) ([]string, error) {
@@ -51,12 +97,16 @@ func retrieveServiceAccountNames(kubeClient *kubernetes.Clientset, namespace str
}
func processNamespaceSA(kubeClient *kubernetes.Clientset, namespace string) ([]string, error) {
- usedServiceAccounts, err := retrieveUsedSA(kubeClient, namespace)
+ usedServiceAccounts, roleServiceAccounts, clusterRoleServiceAccounts, err := retrieveUsedSA(kubeClient, namespace)
if err != nil {
return nil, err
}
usedServiceAccounts = RemoveDuplicatesAndSort(usedServiceAccounts)
+ roleServiceAccounts = RemoveDuplicatesAndSort(roleServiceAccounts)
+ clusterRoleServiceAccounts = RemoveDuplicatesAndSort(clusterRoleServiceAccounts)
+
+ usedServiceAccounts = append(append(usedServiceAccounts, roleServiceAccounts...), clusterRoleServiceAccounts...)
serviceAccountNames, err := retrieveServiceAccountNames(kubeClient, namespace)
if err != nil {