diff --git a/README.md b/README.md index 7decd14..ae595e7 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,6 @@ Ignore control plane pods and nodes in clusters that host the control plane. ``` ./rbac-police eval lib/ --ignore-controlplane ``` -### Nodes don't use NodeAuthorizer -Specify a custom user used by nodes in clusters that don't use the NodeAuthorizer. -``` -./rbac-police eval lib/ --node-user=nodeclient -``` ### Collect once for multiple evaluations ``` ./rbac-police collect -o rbacDb.json @@ -85,6 +80,12 @@ Or: ./rbac-police collect -o rbacDb.json ./rbac-police expand rbacDb.json ``` +### View the permissions of a specific identity +Inspect the permissions of a single identity. +``` +./rbac-police expand -z sa=kube-system:metrics-server +./rbac-police expand -z user=example@email.com +``` ## Documentation - [Policies](docs/policies.md) diff --git a/cmd/eval.go b/cmd/eval.go index 2dbdf4c..ec21f3f 100644 --- a/cmd/eval.go +++ b/cmd/eval.go @@ -14,15 +14,15 @@ import ( // evalCmd represents the eval command var ( - evalConfig eval.EvalConfig - shortMode bool - violations []string - evalCmd = &cobra.Command{ Use: "eval [rbac-json]", Short: "Evaulates RBAC permissions of Kubernetes identities using Rego policies", Run: runEval, } + + evalConfig eval.EvalConfig + shortMode bool + violations []string ) func runEval(cmd *cobra.Command, args []string) { diff --git a/cmd/expand.go b/cmd/expand.go index cf181d5..675f273 100644 --- a/cmd/expand.go +++ b/cmd/expand.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + "strings" "github.com/PaloAltoNetworks/rbac-police/pkg/collect" "github.com/PaloAltoNetworks/rbac-police/pkg/expand" @@ -20,13 +21,29 @@ var ( This is done by repeating the entire permissions of each role under each identity that has it.`, Run: runExpand, } + + zoomedIdentity string ) func runExpand(cmd *cobra.Command, args []string) { var ( - collectResult collect.CollectResult + collectResult collect.CollectResult + output []byte + err error + zoomedType string + zoomedName string + zoomedNamespace string ) + // If zoomedIdentity is used, parse it + if zoomedIdentity != "" { + zoomedType, zoomedName, zoomedNamespace = parseZoomedIdentity(zoomedIdentity) + if zoomedType == "" { + cmd.Help() + return + } + } + // Get RBAC JSON if len(args) > 0 { if collectionOptionsSet() { @@ -57,8 +74,47 @@ func runExpand(cmd *cobra.Command, args []string) { return // error printed by Expand() } + // Marshal results + if zoomedIdentity == "" { + output, err = marshalResults(expandResult) + } else { + // Zoom on a specific identity // TODO: consider only collecting / expanding the zoomed identity + if zoomedType == "sa" { + for _, sa := range expandResult.ServiceAccounts { + if sa.Name == zoomedName && sa.Namespace == zoomedNamespace { + output, err = marshalResults(sa) + break + } + } + } else if zoomedType == "node" { + for _, node := range expandResult.Nodes { + if node.Name == zoomedName { + output, err = marshalResults(node) + break + } + } + } else if zoomedType == "user" { + for _, user := range expandResult.Users { + if user.Name == zoomedName { + output, err = marshalResults(user) + break + } + } + } else if zoomedType == "group" { + for _, grp := range expandResult.Groups { + if grp.Name == zoomedName { + output, err = marshalResults(grp) + break + } + } + } + if len(output) == 0 { + fmt.Println("[!] Cannot find zoomed identity") + return + } + } + // Output expand results - output, err := marshalResults(expandResult) if err != nil { log.Errorln("runExpand: failed to marshal results with", err) return @@ -67,5 +123,35 @@ func runExpand(cmd *cobra.Command, args []string) { } func init() { + expandCmd.Flags().StringVarP(&zoomedIdentity, "zoom", "z", "", "only show the permissions of the specified identity, format is 'type=identity', e.g. 'sa=kube-system:default', 'user=example@email.com'") rootCmd.AddCommand(expandCmd) } + +// Parses zoomedIdentity into a type, identity and namespace +func parseZoomedIdentity(zoomedIdentity string) (string, string, string) { + var zoomedNamespace string + + // Parse type & name + separatorIndex := strings.Index(zoomedIdentity, "=") + if separatorIndex < 0 { + fmt.Println("[!] Cannot parse zoomed identity, format is 'type=identity'") + return "", "", "" + } + zoomedType := zoomedIdentity[:separatorIndex] + zoomedName := zoomedIdentity[separatorIndex+1:] + + // Parse namespace for service accounts + if zoomedType == "sa" { + separatorIndex = strings.Index(zoomedName, ":") + if separatorIndex < 0 { + fmt.Println("[!] Cannot parse zoomed SA, format is 'sa=namespace:name'") + return "", "", "" + } + zoomedNamespace = zoomedName[:separatorIndex] + zoomedName = zoomedName[separatorIndex+1:] + } else if zoomedType != "node" && zoomedType != "user" && zoomedType != "group" { + fmt.Printf("[!] Unsupported type for zoomed identity '%s', supported types are 'sa', 'node', 'user' and 'group'\n", zoomedType) + return "", "", "" + } + return zoomedType, zoomedName, zoomedNamespace +} diff --git a/docs/expand.md b/docs/expand.md index 6a41594..417bcf4 100644 --- a/docs/expand.md +++ b/docs/expand.md @@ -8,6 +8,7 @@ Usage: Flags: -h, --help help for expand + -z, --zoom string only show the permissions of the specified identity, format is 'type=identity', e.g. 'sa=kube-system:default', 'user=example@email.com' Global Flags: -a, --all-serviceaccounts collect data on all serviceAccounts, not only those assigned to a pod