Skip to content

Commit

Permalink
[Command] Analysis (#39)
Browse files Browse the repository at this point in the history
Analyze RBAC permissions and highlight overly permissive principals, risky permissions.
The command allows to use a custom analysis rule set, as well as the ability to define custom exceptions (global and per-rule).
  • Loading branch information
gadinaor-r7 authored Sep 29, 2021
1 parent 2beb5f6 commit 5c72dcf
Show file tree
Hide file tree
Showing 22 changed files with 1,191 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Build
- name: Build & Test
run: |
make get-bins
make test
make gorelease-snapshot
1 change: 1 addition & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
make get-bins
make test
make gorelease
- name: Update new version in krew-index
uses: rajatjindal/[email protected]
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ get-release-bins: ##@build Download goreleaser
build: ##@build Build on local platform
export CGO_ENABLED=0 && go build -o $(BINDIR)/$(BINNAME) -tags staticbinary -v -ldflags '$(LDFLAGS)' github.com/alcideio/rbac-tool

.PHONY: test
test: ##@Test run tests
go test -v github.com/alcideio/rbac-tool/pkg/...

create-kind-cluster: ##@Test creatte KIND cluster
kind create cluster --image kindest/node:v1.18.2 --name rbak
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Usage:
rbac-tool [command]

Available Commands:
analysis Analyze RBAC permissions and highlight overly permissive principals, risky permissions, etc.
auditgen Generate RBAC policy from Kubernetes audit events
bash-completion Generate bash completion. source < (rbac-too bash-completion)
generate Generate Role or ClusterRole and reduce the use of wildcards
Expand All @@ -70,6 +71,7 @@ Use "rbac-tool [command] --help" for more information about a command.
```

- [The `rbac-tool viz` command](#rbac-tool-viz)
- [The `rbac-tool analysis` command](#rbac-tool-analysis)
- [The `rbac-tool lookup` command](#rbac-tool-lookup)
- [The `rbac-tool who-can` command](#rbac-tool-who-can)
- [The `rbac-tool policy-rules` command](#rbac-tool-policy-rules)
Expand Down Expand Up @@ -110,6 +112,26 @@ rbac-tool viz --cluster-context myctx
rbac-tool viz --outformat dot --exclude-namespaces=soemns && cat rbac.dot | dot -Tpng > rbac.png && google-chrome rbac.png
```

# `rbac-tool analysis`

Analyze RBAC permissions and highlight overly permissive principals, risky permissions.
The command allows to use a custom analysis rule set, as well as the ability to define custom exceptions (global and per-rule).

The default rule set can be found [here](pkg/analysis/default-rules.yaml)

Examples:

```shell script
# Analyze the cluster pointed by the kubeconfig context 'myctx' with the internal analysis rule set
rbac-tool analysis --cluster-context myctx
```

```shell script
# Analyze the cluster pointed by kubeconfig with the the provided analysis rule set
rbac-tool analysis --config myruleset.yaml
```


# `rbac-tool lookup`
Lookup of the Roles/ClusterRoles used attached to User/ServiceAccount/Group with or without [regex](https://regex101.com/)

Expand Down
170 changes: 170 additions & 0 deletions cmd/analysis_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package cmd

import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"

"github.com/alcideio/rbac-tool/pkg/analysis"
"github.com/alcideio/rbac-tool/pkg/kube"
"github.com/alcideio/rbac-tool/pkg/rbac"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)

func NewCommandAnalysis() *cobra.Command {

clusterContext := ""
customConfig := ""
output := "table"

// Support overrides
cmd := &cobra.Command{
Use: "analysis",
Aliases: []string{"analyze", "analyze-cluster", "an", "assess"},
Args: cobra.ExactArgs(0),
SilenceUsage: true,
SilenceErrors: true,
Example: "rbac-tool analyze [--config pkg/analysis/default-rules.yaml]",
Short: "Analyze RBAC permissions and highlight overly permissive principals, risky permissions, etc.",
Long: `
Examples:
# Analyze RBAC permissions of the cluster pointed by current context
rbac-tool analyze
`,
Hidden: false,
RunE: func(c *cobra.Command, args []string) error {
var err error

analysisConfig := analysis.DefaultAnalysisConfig()

//Override Rules (if provided)
if customConfig != "" {
analysisConfig, err = analysis.LoadAnalysisConfig(customConfig)
if err != nil {
return err
}
}

client, err := kube.NewClient(clusterContext)
if err != nil {
return fmt.Errorf("Failed to create kubernetes client - %v", err)
}

perms, err := rbac.NewPermissionsFromCluster(client)
if err != nil {
return err
}

permsPerSubject := rbac.NewSubjectPermissions(perms)
policies := rbac.NewSubjectPermissionsList(permsPerSubject)

analyzer := analysis.CreateAnalyzer(analysisConfig, policies)
if analyzer == nil {
return fmt.Errorf("Failed to create analyzer")
}

report, err := analyzer.Analyze()
if err != nil {
return err
}

switch output {
case "table":
rows := [][]string{}

for _, f := range report.Findings {

row := []string{
f.Subject.Kind,
f.Subject.Name,
f.Subject.Namespace,
f.Finding.RuleName,
strings.ToUpper(f.Finding.Severity),

f.Finding.Message,
f.Finding.Recommendation,
strings.Join(f.Finding.References, ","),
}
rows = append(rows, row)
}

sort.Slice(rows, func(i, j int) bool {
if strings.Compare(rows[i][0], rows[j][0]) == 0 {
return (strings.Compare(rows[i][1], rows[j][1]) < 0)
}

return (strings.Compare(rows[i][0], rows[j][0]) < 0)
})

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"TYPE", "SUBJECT", "NAMESPACE", "RULE", "SEVERITY", "INFO", "RECOMMENDATION", "REFERENCES"})
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
//table.SetAutoMergeCells(true)
table.SetBorder(false)
table.SetAlignment(tablewriter.ALIGN_LEFT)
//table.SetAutoMergeCells(true)

table.AppendBulk(rows)
table.Render()

return nil
case "yaml":
data, err := yaml.Marshal(report)
if err != nil {
return fmt.Errorf("Processing error - %v", err)
}
fmt.Println(string(data))
return nil

case "json":
data, err := json.Marshal(report)
if err != nil {
return fmt.Errorf("Processing error - %v", err)
}

fmt.Println(string(data))
return nil

default:
return fmt.Errorf("Unsupported output format")
}
},
}

flags := cmd.Flags()
flags.StringVarP(&customConfig, "config", "c", "", "Load custom analysis customConfig")

flags.StringVar(&clusterContext, "cluster-context", "", "Cluster Context .use 'kubectl config get-contexts' to list available contexts")
flags.StringVarP(&output, "output", "o", "yaml", "Output type: table | json | yaml")

cmd.AddCommand(
NewCommandGenerateAnalysisConfig(),
)

return cmd
}

func NewCommandGenerateAnalysisConfig() *cobra.Command {
return &cobra.Command{
Use: "generate",
Aliases: []string{"gen"},
Hidden: true,
Short: "Generate Analysis Config",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := analysis.ExportDefaultConfig("yaml")
if err != nil {
return err
}

fmt.Println(c)
return nil
},
}
}
6 changes: 3 additions & 3 deletions cmd/auditgen_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ func NewCommandAuditGen() *cobra.Command {
ExpandMultipleNamespacesToClusterScoped: true,
ExpandMultipleNamesToUnnamed: true,
Annotations: map[string]string{
"alcide.io/generated-by": "rbac-tool",
"alcide.io/generated": time.Now().Format(time.RFC3339),
"insightcloudsec.rapid7.com/generated-by": "rbac-tool",
"insightcloudsec.rapid7.com/generated": time.Now().Format(time.RFC3339),
},
}

Expand Down Expand Up @@ -213,7 +213,7 @@ func (a *AuditGenOpts) Run() error {

opts := auditutil.DefaultGenerateOptions()
opts.Annotations = a.Annotations
opts.Name = fmt.Sprintf("alcide.io:%v", sanitizeName(username))
opts.Name = fmt.Sprintf("insightcloudsec:%v", sanitizeName(username))
opts.ExpandMultipleNamespacesToClusterScoped = a.ExpandMultipleNamespacesToClusterScoped
opts.ExpandMultipleNamesToUnnamed = a.ExpandMultipleNamesToUnnamed

Expand Down
3 changes: 1 addition & 2 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

var (
Version = ""
Commit = ""
Commit = ""
)

func NewCommandVersion() *cobra.Command {
Expand All @@ -19,4 +19,3 @@ func NewCommandVersion() *cobra.Command {
},
}
}

4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ require (
github.com/emicklei/dot v0.10.2
github.com/fatih/color v1.7.0
github.com/fatih/structs v1.1.0
github.com/google/cel-go v0.7.3
github.com/google/uuid v1.1.2
github.com/huandu/xstrings v1.3.0 // indirect
github.com/kr/pretty v0.2.0
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5
github.com/spf13/cobra v1.0.0
google.golang.org/protobuf v1.25.0
k8s.io/api v0.19.13
k8s.io/apimachinery v0.19.13
k8s.io/apiserver v0.19.13
Expand Down
Loading

0 comments on commit 5c72dcf

Please sign in to comment.