diff --git a/LICENSE b/LICENSE index 261eeb9e..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/cautils/opapolicy/datastructures.go b/cautils/opapolicy/datastructures.go index 81cd7f08..4c9a25a0 100644 --- a/cautils/opapolicy/datastructures.go +++ b/cautils/opapolicy/datastructures.go @@ -148,3 +148,11 @@ type PolicyIdentifier struct { Kind NotificationPolicyKind `json:"kind"` Name string `json:"name"` } + +type ScanInfo struct { + PolicyIdentifier PolicyIdentifier `json:"policyIdentifier"` + Output string `json:"output"` + ExcludedNamespaces string `json:"excludedNamespaces"` + Input []string `json:"input"` + Silent bool `json:"silent"` +} diff --git a/cmd/framework.go b/cmd/framework.go new file mode 100644 index 00000000..27b4b6b9 --- /dev/null +++ b/cmd/framework.go @@ -0,0 +1,131 @@ +package cmd + +import ( + "errors" + "fmt" + "io/ioutil" + "kube-escape/cautils" + "kube-escape/cautils/armotypes" + "kube-escape/cautils/k8sinterface" + "kube-escape/cautils/opapolicy" + "kube-escape/opaprocessor" + "kube-escape/policyhandler" + "kube-escape/printer" + "os" + "strings" + + "github.com/spf13/cobra" +) + +var scanInfo opapolicy.ScanInfo + +type CLIHandler struct { + policyHandler *policyhandler.PolicyHandler + scanInfo *opapolicy.ScanInfo +} + +var frameworkCmd = &cobra.Command{ + Use: "framework ", + Short: "The framework you wish to use. Supported frameworks: nsa, mitre", + Long: ``, + ValidArgs: []string{"nsa", "mitre"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires at least one argument") + } + if !isValidFramework(args[0]) { + return errors.New("supported frameworks: nsa and mitre") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + scanInfo.PolicyIdentifier = opapolicy.PolicyIdentifier{} + scanInfo.PolicyIdentifier.Kind = opapolicy.KindFramework + scanInfo.PolicyIdentifier.Name = args[0] + scanInfo.Input = args[1:] + CliSetup() + }, +} + +func isValidFramework(framework string) bool { + return framework == "nsa" || framework != "mitre" +} + +func init() { + scanCmd.AddCommand(frameworkCmd) + scanInfo = opapolicy.ScanInfo{} + frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "excluded-namespaces", "e", "", "namespaces to exclude from check") + frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "pretty-printer", "output format") + frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "silent output") + +} + +func processYamlInput(yamls string) { + listOfYamls := strings.Split(yamls, ",") + for _, yaml := range listOfYamls { + dat, err := ioutil.ReadFile(yaml) + if err != nil { + fmt.Printf("Could not open file: %s.", yaml) + } + fmt.Print(string(dat)) + } + +} + +func CliSetup() error { + k8s := k8sinterface.NewKubernetesApi() + + processNotification := make(chan *cautils.OPASessionObj) + reportResults := make(chan *cautils.OPASessionObj) + + // policy handler setup + policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s) + + // cli handler setup + cli := NewCLIHandler(policyHandler) + if err := cli.Scan(); err != nil { + panic(err) + } + + // processor setup - rego run + go func() { + reporterObj := opaprocessor.NewOPAProcessor(&processNotification, &reportResults) + reporterObj.ProcessRulesListenner() + }() + p := printer.NewPrinter(&reportResults, scanInfo.Output) + p.ActionPrint() + + return nil +} + +func NewCLIHandler(policyHandler *policyhandler.PolicyHandler) *CLIHandler { + return &CLIHandler{ + scanInfo: &scanInfo, + policyHandler: policyHandler, + } +} + +func (clihandler *CLIHandler) Scan() error { + cautils.InfoDisplay(os.Stdout, "ARMO security scanner starting\n") + + policyNotification := &opapolicy.PolicyNotification{ + NotificationType: opapolicy.TypeExecPostureScan, + Rules: []opapolicy.PolicyIdentifier{ + *&clihandler.scanInfo.PolicyIdentifier, + }, + Designators: armotypes.PortalDesignator{}, + } + + switch policyNotification.NotificationType { + case opapolicy.TypeExecPostureScan: + go func() { + if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, scanInfo.ExcludedNamespaces); err != nil { + fmt.Printf("%v\n", err) + os.Exit(0) + } + }() + default: + return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType) + } + return nil +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..c80ca026 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var cfgFile string + +var rootCmd = &cobra.Command{ + Use: "kubescape", + Short: "A tool for running NSA recommended tests in your cluster ", + Long: `Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in Kubernetes Hardening Guidance +by to NSA and CISA Tests are configured with YAML files, making this tool easy to update as test specifications evolve.`, +} + +func Execute() { + rootCmd.Execute() +} + +func init() { + cobra.OnInitialize(initConfig) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { +} diff --git a/cmd/scan.go b/cmd/scan.go new file mode 100644 index 00000000..98d6bdab --- /dev/null +++ b/cmd/scan.go @@ -0,0 +1,18 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// scanCmd represents the scan command +var scanCmd = &cobra.Command{ + Use: "scan", + Short: "Scan command", + Long: `The action you want to perform`, + Run: func(cmd *cobra.Command, args []string) { + }, +} + +func init() { + rootCmd.AddCommand(scanCmd) +} diff --git a/go.mod b/go.mod index ce3983ca..58d2112e 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/enescakir/emoji v1.0.0 github.com/fatih/color v1.12.0 github.com/francoispqt/gojay v1.2.13 + github.com/fsnotify/fsnotify v1.5.0 // indirect github.com/gofrs/uuid v4.0.0+incompatible github.com/golang/glog v0.0.0-20210429001901-424d2337a529 github.com/mattn/go-isatty v0.0.13 @@ -21,7 +22,10 @@ require ( github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/satori/go.uuid v1.2.0 + github.com/spf13/cobra v1.2.1 golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 + golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect + golang.org/x/text v0.3.7 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect k8s.io/api v0.22.0 k8s.io/apimachinery v0.22.0 diff --git a/inputhandler/clihandler/clihandler.go b/inputhandler/clihandler/clihandler.go deleted file mode 100644 index c1fe128a..00000000 --- a/inputhandler/clihandler/clihandler.go +++ /dev/null @@ -1,52 +0,0 @@ -package clihandler - -import ( - "fmt" - "kube-escape/cautils" - "kube-escape/policyhandler" - "os" - - "kube-escape/cautils/armotypes" - "kube-escape/cautils/opapolicy" -) - -type CLIHandler struct { - policyHandler *policyhandler.PolicyHandler - flagHandler FlagHandler -} - -func NewCLIHandler(policyHandler *policyhandler.PolicyHandler) *CLIHandler { - return &CLIHandler{ - flagHandler: *NewFlagHandler(), - policyHandler: policyHandler, - } -} - -func (clihandler *CLIHandler) Scan() error { - clihandler.flagHandler.ParseFlag() - if !clihandler.flagHandler.ExecuteScan() { - os.Exit(0) - } - cautils.InfoDisplay(os.Stdout, "ARMO security scanner starting\n") - - policyNotification := &opapolicy.PolicyNotification{ - NotificationType: opapolicy.TypeExecPostureScan, - Rules: []opapolicy.PolicyIdentifier{ - *clihandler.flagHandler.policyIdentifier, - }, - Designators: armotypes.PortalDesignator{}, - } - - switch policyNotification.NotificationType { - case opapolicy.TypeExecPostureScan: - go func() { - if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification); err != nil { - fmt.Printf("%v\n", err) - os.Exit(0) - } - }() - default: - return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType) - } - return nil -} diff --git a/main.go b/main.go index 36312624..4f4ce7ac 100644 --- a/main.go +++ b/main.go @@ -1,49 +1,7 @@ package main -import ( - "fmt" - "kube-escape/cautils" - k8sinterface "kube-escape/cautils/k8sinterface" - "kube-escape/inputhandler/clihandler" - - "kube-escape/opaprocessor" - "kube-escape/policyhandler" - "kube-escape/printer" - - "os" -) +import "kube-escape/cmd" func main() { - - if err := CliSetup(); err != nil { - fmt.Println(err.Error()) - os.Exit(1) - } - -} - -func CliSetup() error { - k8s := k8sinterface.NewKubernetesApi() - - processNotification := make(chan *cautils.OPASessionObj) - reportResults := make(chan *cautils.OPASessionObj) - - // policy handler setup - policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s) - - // cli handler setup - cli := clihandler.NewCLIHandler(policyHandler) - if err := cli.Scan(); err != nil { - panic(err) - } - - // processor setup - rego run - go func() { - reporterObj := opaprocessor.NewOPAProcessor(&processNotification, &reportResults) - reporterObj.ProcessRulesListenner() - }() - p := printer.NewPrinter(&reportResults, printer.PrettyPrinter) - p.ActionPrint() - - return nil + cmd.Execute() } diff --git a/policyhandler/__debug_bin b/policyhandler/__debug_bin deleted file mode 100644 index 0cbfc5ed..00000000 Binary files a/policyhandler/__debug_bin and /dev/null differ diff --git a/policyhandler/handlenotification.go b/policyhandler/handlenotification.go index d577fe3b..0f0c2c00 100644 --- a/policyhandler/handlenotification.go +++ b/policyhandler/handlenotification.go @@ -1,7 +1,6 @@ package policyhandler import ( - "flag" "fmt" "kube-escape/cautils" @@ -27,7 +26,7 @@ func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, k8s *k8sinterf } } -func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opapolicy.PolicyNotification) error { +func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opapolicy.PolicyNotification, excludedNamespaces string) error { glog.Infof("Processing notification. reportID: %s", notification.ReportID) opaSessionObj := cautils.NewOPASessionObj(nil, nil) // validate notification @@ -53,10 +52,7 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opap // get k8s resources cautils.ProgressTextDisplay("Accessing Kubernetes objects") glog.Infof(fmt.Sprintf("Getting kubernetes objects. reportID: %s", notification.ReportID)) - excludedNamespaces := "" - if flag.Arg(3) == "--exclude-namespaces" { - excludedNamespaces = flag.Arg(4) - } + k8sResources, err := policyHandler.getK8sResources(frameworks, ¬ification.Designators, excludedNamespaces) if err != nil || len(*k8sResources) == 0 { glog.Error(err) diff --git a/policyhandler/handlepullpolicies.go b/policyhandler/handlepullpolicies.go index 95a75f3b..926b0a51 100644 --- a/policyhandler/handlepullpolicies.go +++ b/policyhandler/handlepullpolicies.go @@ -61,7 +61,7 @@ func (db *ArmoAPI) GetHttpClient() *http.Client { func (db *ArmoAPI) OPAFRAMEWORKGet(name string) ([]opapolicy.Framework, error) { requestURI := "v1/armoFrameworks" requestURI += fmt.Sprintf("?customerGUID=%s", "11111111-1111-1111-1111-111111111111") - requestURI += fmt.Sprintf("&frameworkName=%s", name) + requestURI += fmt.Sprintf("&frameworkName=%s", strings.ToUpper(name)) requestURI += "&getRules=true" fullURL := URLEncoder(fmt.Sprintf("%s/%s", db.GetServerAddress(), requestURI)) diff --git a/printer/printresults.go b/printer/printresults.go index c5d649fb..199ca2ea 100644 --- a/printer/printresults.go +++ b/printer/printresults.go @@ -19,8 +19,8 @@ var INDENT = " " const ( PrettyPrinter string = "pretty-printer" - JsonPrinter string = "json-printer" - JunitResultPrinter string = "junit-result-printer" + JsonPrinter string = "json" + JunitResultPrinter string = "junit" ) type Printer struct { @@ -91,6 +91,7 @@ func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) { TotalFailed: len(workloadsSummery), WorkloadSummery: mapResources, Description: cr.Description, + Remediation: cr.Remediation, } } } @@ -115,7 +116,12 @@ func (print *Printer) printSummery(controlName string, controlSummery *ControlSu cautils.SimpleDisplay(os.Stdout, "Summary - ") cautils.SuccessDisplay(os.Stdout, "Passed:%v ", controlSummery.TotalResources-controlSummery.TotalFailed) cautils.FailureDisplay(os.Stdout, "Failed:%v ", controlSummery.TotalFailed) - cautils.InfoDisplay(os.Stdout, "Total:%v\n\n", controlSummery.TotalResources) + cautils.InfoDisplay(os.Stdout, "Total:%v\n", controlSummery.TotalResources) + if controlSummery.TotalFailed > 0 { + cautils.DescriptionDisplay(os.Stdout, "Remediation: %v\n", controlSummery.Remediation) + } + cautils.DescriptionDisplay(os.Stdout, "\n") + } func (printer *Printer) printTitle(controlName string, controlSummery *ControlSummery) { diff --git a/printer/summary.go b/printer/summary.go index 9b245488..3a2eebbc 100644 --- a/printer/summary.go +++ b/printer/summary.go @@ -14,6 +14,7 @@ type ControlSummery struct { TotalResources int TotalFailed int Description string + Remediation string WorkloadSummery map[string][]WorkloadSummery // :[] }