mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-19 20:39:56 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca927dec30 | ||
|
|
3a78ef46a3 | ||
|
|
bdb1cd0905 | ||
|
|
ffb556a637 | ||
|
|
40acfb5e9d | ||
|
|
de8bcfa0d2 | ||
|
|
9439f407da | ||
|
|
5095e62961 | ||
|
|
3301907864 | ||
|
|
151175c40f | ||
|
|
234d4fa537 | ||
|
|
66068757e1 | ||
|
|
8a7cda5dd1 | ||
|
|
a0ca68cc41 | ||
|
|
41cae0bc93 | ||
|
|
b4198fde8c | ||
|
|
bd24f35738 | ||
|
|
6fcbb757b5 | ||
|
|
3b8825e5d2 | ||
|
|
5cf3244918 | ||
|
|
934c9ccc8b | ||
|
|
41dfdfd1e8 | ||
|
|
427fb59c99 | ||
|
|
ae825800f6 | ||
|
|
d72700acf6 | ||
|
|
3310a6a26f | ||
|
|
740b5aa772 | ||
|
|
04b55e764a | ||
|
|
beb4062bb1 | ||
|
|
5d4cd4acdc | ||
|
|
aec8198131 | ||
|
|
0a850e47df | ||
|
|
4f466d517a |
@@ -87,13 +87,13 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
|
||||
### Examples
|
||||
|
||||
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to [ARMO portal](https://portal.armo.cloud/)
|
||||
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework nsa --submit
|
||||
```
|
||||
|
||||
|
||||
* Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to [ARMO portal](https://portal.armo.cloud/)
|
||||
* Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework mitre --submit
|
||||
```
|
||||
@@ -130,6 +130,10 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --form
|
||||
kubescape scan framework nsa --exceptions examples/exceptions.json
|
||||
```
|
||||
|
||||
### Repeatedly Kubescape Scanning using a CronJob
|
||||
|
||||
For setting up a cronJob please follow the [instructions](examples/cronJob-support/README.md)
|
||||
|
||||
### Helm Support
|
||||
|
||||
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
|
||||
|
||||
@@ -109,8 +109,10 @@ func ClusterConfigSetup(scanInfo *ScanInfo, k8s *k8sinterface.KubernetesApi, beA
|
||||
return NewEmptyConfig() // local - Delete local config & Do not send report
|
||||
}
|
||||
if scanInfo.Local {
|
||||
scanInfo.Submit = false
|
||||
return NewEmptyConfig() // local - Do not send report
|
||||
}
|
||||
scanInfo.Submit = true
|
||||
return clusterConfig // submit/default - Submit report
|
||||
}
|
||||
|
||||
@@ -127,7 +129,7 @@ func (c *EmptyConfig) GetCustomerGUID() string { return "" }
|
||||
func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj
|
||||
func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() }
|
||||
func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj
|
||||
func (c *EmptyConfig) GetClusterName() string { return k8sinterface.GetClusterName() }
|
||||
func (c *EmptyConfig) GetClusterName() string { return adoptClusterName(k8sinterface.GetClusterName()) }
|
||||
func (c *EmptyConfig) GenerateURL() {
|
||||
message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL())
|
||||
InfoTextDisplay(os.Stdout, fmt.Sprintf("\n%s\n", message))
|
||||
@@ -158,6 +160,7 @@ func (c *ClusterConfig) GetDefaultNS() string { return c.defau
|
||||
func (c *ClusterConfig) GetBackendAPI() getter.IBackend { return c.backendAPI }
|
||||
|
||||
func (c *ClusterConfig) GenerateURL() {
|
||||
message := "Checkout for more cool features: "
|
||||
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
@@ -165,9 +168,8 @@ func (c *ClusterConfig) GenerateURL() {
|
||||
if c.configObj == nil {
|
||||
return
|
||||
}
|
||||
message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL())
|
||||
if c.configObj.CustomerAdminEMail != "" {
|
||||
InfoTextDisplay(os.Stdout, message+"\n")
|
||||
InfoTextDisplay(os.Stdout, "\n\n"+message+u.String()+"\n\n")
|
||||
return
|
||||
}
|
||||
u.Path = "account/sign-up"
|
||||
@@ -176,8 +178,7 @@ func (c *ClusterConfig) GenerateURL() {
|
||||
q.Add("customerGUID", c.configObj.CustomerGUID)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
InfoTextDisplay(os.Stdout, message+"\n")
|
||||
|
||||
InfoTextDisplay(os.Stdout, "\n\n"+message+u.String()+"\n\n")
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetCustomerGUID() string {
|
||||
@@ -242,7 +243,7 @@ func (c *ClusterConfig) setCustomerGUID(customerGUID string) {
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) setClusterName(clusterName string) {
|
||||
c.configObj.ClusterName = clusterName
|
||||
c.configObj.ClusterName = adoptClusterName(clusterName)
|
||||
}
|
||||
func (c *ClusterConfig) GetClusterName() string {
|
||||
return c.configObj.ClusterName
|
||||
@@ -471,3 +472,7 @@ func DeleteConfigMap(k8s *k8sinterface.KubernetesApi) error {
|
||||
func DeleteConfigFile() error {
|
||||
return os.Remove(ConfigFileFullPath())
|
||||
}
|
||||
|
||||
func adoptClusterName(clusterName string) string {
|
||||
return strings.ReplaceAll(clusterName, "/", "-")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type DownloadReleasedPolicy struct {
|
||||
|
||||
func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
|
||||
return &DownloadReleasedPolicy{
|
||||
gs: gitregostore.InitDefaultGitRegoStore(),
|
||||
gs: gitregostore.InitDefaultGitRegoStore(-1),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ const DefaultLocalStore = ".kubescape"
|
||||
|
||||
// Load policies from a local repository
|
||||
type LoadPolicy struct {
|
||||
filePath string
|
||||
filePaths []string
|
||||
}
|
||||
|
||||
func NewLoadPolicy(filePath string) *LoadPolicy {
|
||||
func NewLoadPolicy(filePaths []string) *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePath: filePath,
|
||||
filePaths: filePaths,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,37 +30,58 @@ func NewLoadPolicy(filePath string) *LoadPolicy {
|
||||
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
|
||||
|
||||
control := &reporthandling.Control{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
filePath := lp.getFileForControl()
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, control)
|
||||
if err = json.Unmarshal(f, control); err != nil {
|
||||
return control, err
|
||||
}
|
||||
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
|
||||
return nil, fmt.Errorf("control from file not matching")
|
||||
framework, err := lp.GetFramework(controlName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("control from file not matching")
|
||||
} else {
|
||||
for _, ctrl := range framework.Controls {
|
||||
if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) {
|
||||
control = &ctrl
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return control, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
for _, filePath := range lp.filePaths {
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, framework)
|
||||
if err = json.Unmarshal(f, framework); err != nil {
|
||||
return framework, err
|
||||
}
|
||||
if strings.EqualFold(frameworkName, framework.Name) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
|
||||
|
||||
return nil, fmt.Errorf("framework from file not matching")
|
||||
}
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
|
||||
filePath := lp.getFileForException()
|
||||
exception := []armotypes.PostureExceptionPolicy{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -68,3 +89,11 @@ func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotyp
|
||||
err = json.Unmarshal(f, &exception)
|
||||
return exception, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) getFileForException() string {
|
||||
return lp.filePaths[0]
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) getFileForControl() string {
|
||||
return lp.filePaths[0]
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@ package cautils
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type ScanInfo struct {
|
||||
Getters
|
||||
PolicyIdentifier reporthandling.PolicyIdentifier
|
||||
PolicyIdentifier []reporthandling.PolicyIdentifier
|
||||
UseExceptions string // Load exceptions configuration
|
||||
UseFrom string // Load framework from local file (instead of download). Use when running offline
|
||||
UseFrom []string // Load framework from local file (instead of download). Use when running offline
|
||||
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
|
||||
Format string // Format results (table, json, junit ...)
|
||||
Output string // Store results in an output file, Output file name
|
||||
@@ -42,22 +41,21 @@ func (scanInfo *ScanInfo) Init() {
|
||||
func (scanInfo *ScanInfo) setUseExceptions() {
|
||||
if scanInfo.UseExceptions != "" {
|
||||
// load exceptions from file
|
||||
scanInfo.ExceptionsGetter = getter.NewLoadPolicy(scanInfo.UseExceptions)
|
||||
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
|
||||
} else {
|
||||
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
|
||||
}
|
||||
|
||||
}
|
||||
func (scanInfo *ScanInfo) setUseFrom() {
|
||||
if scanInfo.UseFrom != "" {
|
||||
return
|
||||
}
|
||||
if scanInfo.UseDefault {
|
||||
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json")
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
|
||||
}
|
||||
}
|
||||
}
|
||||
func (scanInfo *ScanInfo) setGetter() {
|
||||
if scanInfo.UseFrom != "" {
|
||||
if len(scanInfo.UseFrom) > 0 {
|
||||
// load from file
|
||||
scanInfo.PolicyGetter = getter.NewLoadPolicy(scanInfo.UseFrom)
|
||||
} else {
|
||||
@@ -84,22 +82,3 @@ func (scanInfo *ScanInfo) setOutputFile() {
|
||||
func (scanInfo *ScanInfo) ScanRunningCluster() bool {
|
||||
return len(scanInfo.InputPatterns) == 0
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) SetClusterConfig() (IClusterConfig, *k8sinterface.KubernetesApi) {
|
||||
var clusterConfig IClusterConfig
|
||||
var k8s *k8sinterface.KubernetesApi
|
||||
if !scanInfo.ScanRunningCluster() {
|
||||
k8sinterface.ConnectedToCluster = false
|
||||
clusterConfig = NewEmptyConfig()
|
||||
} else {
|
||||
k8s = k8sinterface.NewKubernetesApi()
|
||||
// setup cluster config
|
||||
clusterConfig = ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector())
|
||||
}
|
||||
return clusterConfig, k8s
|
||||
}
|
||||
|
||||
// func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool {
|
||||
// _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
|
||||
// return err == nil
|
||||
// }
|
||||
|
||||
@@ -13,25 +13,43 @@ import (
|
||||
|
||||
// controlCmd represents the control command
|
||||
var controlCmd = &cobra.Command{
|
||||
Use: "control <control name>/<control id>",
|
||||
Use: "control <control names list>/<control ids list>.\nExamples:\n$ kubescape scan control C-0058,C-0057 [flags]\n$ kubescape scan contol C-0058 [flags]\n$ kubescape scan control 'privileged container,allowed hostpath' [flags]",
|
||||
Short: fmt.Sprintf("The control you wish to use for scan. It must be present in at least one of the folloiwng frameworks: %s", clihandler.ValidFrameworks),
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) {
|
||||
return fmt.Errorf("requires at least one argument")
|
||||
if len(args) > 0 {
|
||||
controls := strings.Split(args[0], ",")
|
||||
if len(controls) > 1 {
|
||||
if controls[1] == "" {
|
||||
return fmt.Errorf("usage: <control_one>,<control_two>")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one control name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
flagValidationControl()
|
||||
scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{}
|
||||
if !(cmd.Flags().Lookup("use-from").Changed) {
|
||||
scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0])
|
||||
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
|
||||
|
||||
if len(args) < 1 {
|
||||
scanInfo.PolicyIdentifier = SetScanForGivenFrameworks(clihandler.SupportedFrameworks)
|
||||
} else {
|
||||
var controls []string
|
||||
if len(args) > 0 {
|
||||
controls = strings.Split(args[0], ",")
|
||||
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
|
||||
scanInfo.PolicyIdentifier = setScanForFirstControl(controls)
|
||||
}
|
||||
|
||||
if len(controls) > 1 {
|
||||
scanInfo.PolicyIdentifier = SetScanForGivenControls(controls[1:])
|
||||
}
|
||||
}
|
||||
scanInfo.FrameworkScan = false
|
||||
scanInfo.PolicyIdentifier.Kind = reporthandling.KindControl
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := clihandler.CliSetup(scanInfo)
|
||||
err := clihandler.CliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -51,3 +69,22 @@ func flagValidationControl() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func setScanForFirstControl(controls []string) []reporthandling.PolicyIdentifier {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindControl
|
||||
newPolicy.Name = controls[0]
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
|
||||
func SetScanForGivenControls(controls []string) []reporthandling.PolicyIdentifier {
|
||||
for _, control := range controls {
|
||||
control := strings.TrimLeft(control, " ")
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindControl
|
||||
newPolicy.Name = control
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
|
||||
@@ -9,32 +9,42 @@ import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var frameworkCmd = &cobra.Command{
|
||||
|
||||
Use: fmt.Sprintf("framework <framework name> [`<glob pattern>`/`-`] [flags]\nSupported frameworks: %s", clihandler.ValidFrameworks),
|
||||
Use: fmt.Sprintf("framework <framework names list> [`<glob pattern>`/`-`] [flags]\nExamples:\n$ kubescape scan framework nsa [flags]\n$ kubescape scan framework mitre,nsa [flags]\n$ kubescape scan framework 'nsa, mitre' [flags]\nSupported frameworks: %s", clihandler.ValidFrameworks),
|
||||
Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", ")),
|
||||
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
|
||||
ValidArgs: clihandler.SupportedFrameworks,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) {
|
||||
return fmt.Errorf("requires at least one argument")
|
||||
} else if len(args) > 0 {
|
||||
if !isValidFramework(strings.ToLower(args[0])) {
|
||||
return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", ")))
|
||||
if len(args) > 0 {
|
||||
// "nsa, mitre" -> ["nsa", "mitre"] and nsa,mitre -> ["nsa", "mitre"]
|
||||
frameworks := strings.Split(strings.Join(strings.Fields(args[0]), ""), ",")
|
||||
for _, framework := range frameworks {
|
||||
if !isValidFramework(strings.ToLower(framework)) {
|
||||
return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", ")))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one framework name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{}
|
||||
scanInfo.PolicyIdentifier.Kind = reporthandling.KindFramework
|
||||
flagValidationFramework()
|
||||
if !(cmd.Flags().Lookup("use-from").Changed) {
|
||||
scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0])
|
||||
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
|
||||
// If no framework provided, use all
|
||||
if len(args) < 1 {
|
||||
scanInfo.PolicyIdentifier = SetScanForGivenFrameworks(clihandler.SupportedFrameworks)
|
||||
} else {
|
||||
// Read frameworks from input args
|
||||
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
|
||||
frameworks := strings.Split(strings.Join(strings.Fields(args[0]), ""), ",")
|
||||
scanInfo.PolicyIdentifier = SetScanForFirstFramework(frameworks)
|
||||
if len(frameworks) > 1 {
|
||||
scanInfo.PolicyIdentifier = SetScanForGivenFrameworks(frameworks[1:])
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
@@ -54,7 +64,7 @@ var frameworkCmd = &cobra.Command{
|
||||
}
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := clihandler.CliSetup(scanInfo)
|
||||
err := clihandler.CliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -74,11 +84,26 @@ func init() {
|
||||
frameworkCmd.Flags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
|
||||
frameworkCmd.Flags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
|
||||
frameworkCmd.Flags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
}
|
||||
func SetScanForGivenFrameworks(frameworks []string) []reporthandling.PolicyIdentifier {
|
||||
for _, framework := range frameworks {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindFramework
|
||||
newPolicy.Name = framework
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
|
||||
func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindFramework
|
||||
newPolicy.Name = frameworks[0]
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
|
||||
func flagValidationFramework() {
|
||||
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
fmt.Println("You can use `keep-local` or `submit`, but not both")
|
||||
os.Exit(1)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -16,15 +17,19 @@ var scanCmd = &cobra.Command{
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Long: `The action you want to perform`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("requires one argument: framework/control")
|
||||
}
|
||||
if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0])
|
||||
if len(args) > 0 {
|
||||
if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
frameworkArgs := []string{clihandler.ValidFrameworks}
|
||||
frameworkArgs = append(frameworkArgs, args...)
|
||||
frameworkCmd.RunE(cmd, frameworkArgs)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -35,7 +40,7 @@ func init() {
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
|
||||
scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code 1")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseFrom, "use-from", "", "Load local framework object from specified path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local framework object from default path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from Armo management portal")
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func GetLatestVersion() (string, error) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || 301 < resp.StatusCode {
|
||||
return "unknown", fmt.Errorf("failed to download file, status code: %s", resp.Status)
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
@@ -6,9 +6,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/opaprocessor"
|
||||
"github.com/armosec/kubescape/policyhandler"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/kubescape/resultshandling"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
@@ -23,25 +26,77 @@ type CLIHandler struct {
|
||||
var SupportedFrameworks = []string{"nsa", "mitre"}
|
||||
var ValidFrameworks = strings.Join(SupportedFrameworks, ", ")
|
||||
|
||||
func CliSetup(scanInfo cautils.ScanInfo) error {
|
||||
type componentInterfaces struct {
|
||||
clusterConfig cautils.IClusterConfig
|
||||
resourceHandler resourcehandler.IResourceHandler
|
||||
report reporter.IReport
|
||||
printerHandler printer.IPrinter
|
||||
}
|
||||
|
||||
clusterConfig, k8s := scanInfo.SetClusterConfig()
|
||||
func getReporter(scanInfo *cautils.ScanInfo) reporter.IReport {
|
||||
if !scanInfo.Submit {
|
||||
return reporter.NewReportMock()
|
||||
}
|
||||
if !scanInfo.FrameworkScan {
|
||||
return reporter.NewReportMock()
|
||||
}
|
||||
|
||||
return reporter.NewReportEventReceiver()
|
||||
}
|
||||
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
var resourceHandler resourcehandler.IResourceHandler
|
||||
var clusterConfig cautils.IClusterConfig
|
||||
var reportHandler reporter.IReport
|
||||
|
||||
if !scanInfo.ScanRunningCluster() {
|
||||
k8sinterface.ConnectedToCluster = false
|
||||
clusterConfig = cautils.NewEmptyConfig()
|
||||
|
||||
// load fom file
|
||||
resourceHandler = resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns)
|
||||
|
||||
// set mock report (do not send report)
|
||||
reportHandler = reporter.NewReportMock()
|
||||
} else {
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
resourceHandler = resourcehandler.NewK8sResourceHandler(k8s, scanInfo.ExcludedNamespaces)
|
||||
clusterConfig = cautils.ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector())
|
||||
|
||||
// setup reporter
|
||||
reportHandler = getReporter(scanInfo)
|
||||
}
|
||||
|
||||
// setup printer
|
||||
printerHandler := printer.GetPrinter(scanInfo.Format)
|
||||
printerHandler.SetWriter(scanInfo.Output)
|
||||
|
||||
return componentInterfaces{
|
||||
clusterConfig: clusterConfig,
|
||||
resourceHandler: resourceHandler,
|
||||
report: reportHandler,
|
||||
printerHandler: printerHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func CliSetup(scanInfo *cautils.ScanInfo) error {
|
||||
|
||||
interfaces := getInterfaces(scanInfo)
|
||||
|
||||
processNotification := make(chan *cautils.OPASessionObj)
|
||||
reportResults := make(chan *cautils.OPASessionObj)
|
||||
|
||||
// policy handler setup
|
||||
policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s)
|
||||
|
||||
if err := clusterConfig.SetConfig(scanInfo.Account); err != nil {
|
||||
if err := interfaces.clusterConfig.SetConfig(scanInfo.Account); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
cautils.ClusterName = clusterConfig.GetClusterName()
|
||||
cautils.CustomerGUID = clusterConfig.GetCustomerGUID()
|
||||
|
||||
cautils.ClusterName = interfaces.clusterConfig.GetClusterName() // TODO - Deprecated
|
||||
cautils.CustomerGUID = interfaces.clusterConfig.GetCustomerGUID() // TODO - Deprecated
|
||||
interfaces.report.SetClusterName(interfaces.clusterConfig.GetClusterName())
|
||||
interfaces.report.SetCustomerGUID(interfaces.clusterConfig.GetCustomerGUID())
|
||||
// cli handler setup
|
||||
go func() {
|
||||
// policy handler setup
|
||||
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
|
||||
cli := NewCLIHandler(policyHandler, scanInfo)
|
||||
if err := cli.Scan(); err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -55,13 +110,11 @@ func CliSetup(scanInfo cautils.ScanInfo) error {
|
||||
opaprocessorObj.ProcessRulesListenner()
|
||||
}()
|
||||
|
||||
resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output))
|
||||
resultsHandling := resultshandling.NewResultsHandler(&reportResults, interfaces.report, interfaces.printerHandler)
|
||||
score := resultsHandling.HandleResults(scanInfo)
|
||||
|
||||
// print report url
|
||||
if scanInfo.FrameworkScan {
|
||||
clusterConfig.GenerateURL()
|
||||
}
|
||||
interfaces.clusterConfig.GenerateURL()
|
||||
|
||||
adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100
|
||||
if score < adjustedFailThreshold {
|
||||
@@ -71,9 +124,9 @@ func CliSetup(scanInfo cautils.ScanInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCLIHandler(policyHandler *policyhandler.PolicyHandler, scanInfo cautils.ScanInfo) *CLIHandler {
|
||||
func NewCLIHandler(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) *CLIHandler {
|
||||
return &CLIHandler{
|
||||
scanInfo: &scanInfo,
|
||||
scanInfo: scanInfo,
|
||||
policyHandler: policyHandler,
|
||||
}
|
||||
}
|
||||
@@ -82,10 +135,8 @@ func (clihandler *CLIHandler) Scan() error {
|
||||
cautils.ScanStartDisplay()
|
||||
policyNotification := &reporthandling.PolicyNotification{
|
||||
NotificationType: reporthandling.TypeExecPostureScan,
|
||||
Rules: []reporthandling.PolicyIdentifier{
|
||||
clihandler.scanInfo.PolicyIdentifier,
|
||||
},
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
Rules: clihandler.scanInfo.PolicyIdentifier,
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
}
|
||||
switch policyNotification.NotificationType {
|
||||
case reporthandling.TypeExecPostureScan:
|
||||
|
||||
83
examples/cronJob-support/README.md
Normal file
83
examples/cronJob-support/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Repeatedly Kubescape Scanning
|
||||
|
||||
You can scan your cluster repeatedly by adding a `CronJob` that will repeatedly trigger kubescape
|
||||
|
||||
* Setup [scanning & submitting](#scanning-&-submitting)
|
||||
* Setup [scanning without submitting](#scanning-without-submitting)
|
||||
|
||||
## Scanning & Submitting
|
||||
|
||||
If you wish to repeatedly scan and submit the result to the [Kubescape SaaS version](https://portal.armo.cloud/) where you can benefit the features the SaaS version provides, please follow this instructions ->
|
||||
|
||||
1. Apply kubescape namespace
|
||||
```
|
||||
kubectl apply ks-namespace.yaml
|
||||
```
|
||||
|
||||
2. Apply serviceAccount and roles
|
||||
```
|
||||
kubectl apply ks-serviceAccount.yaml
|
||||
```
|
||||
|
||||
3. Setup and apply configMap
|
||||
|
||||
Before you apply the configMap you need to set the account ID and cluster name in the `ks-configMap.yaml` file.
|
||||
|
||||
* Set cluster name:
|
||||
Run `kubectl config current-context` and set the result in the `data.clusterName` field
|
||||
* Set account ID:
|
||||
1. Navigate to the [Kubescape SaaS version](https://portal.armo.cloud/) and login/sign up for free
|
||||
2. Click the `Add Cluster` button on the top right of the page
|
||||
<img src="screenshots/add-cluster.png" alt="add-cluster">
|
||||
3. Copy the value of `--account` and set it in the `data.customerGUID` field
|
||||
<img src="screenshots/account.png" alt="account">
|
||||
|
||||
Make sure the configMap looks as following;
|
||||
```
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"customerGUID": "XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
||||
"clusterName": "my-awesome-cluster-name"
|
||||
}
|
||||
```
|
||||
|
||||
Finally, apply the configMap
|
||||
```
|
||||
kubectl apply ks-configMap.yaml
|
||||
```
|
||||
|
||||
4. Apply CronJob
|
||||
|
||||
Before you apply the cronJob, make sure the scanning frequency suites your needs
|
||||
```
|
||||
kubectl apply ks-cronJob-submit.yaml
|
||||
```
|
||||
|
||||
## Scanning Without Submitting
|
||||
|
||||
If you wish to repeatedly scan but not submit the scan results, follow this instructions ->
|
||||
|
||||
1. Apply kubescape namespace
|
||||
```
|
||||
kubectl apply ks-namespace.yaml
|
||||
```
|
||||
|
||||
2. Apply serviceAccount and roles
|
||||
```
|
||||
kubectl apply ks-serviceAccount.yaml
|
||||
```
|
||||
|
||||
3. Apply CronJob
|
||||
|
||||
Before you apply the cronJob, make sure the scanning frequency suites your needs
|
||||
```
|
||||
kubectl apply ks-cronJob-non-submit.yaml
|
||||
```
|
||||
14
examples/cronJob-support/ks-configMap.yaml
Normal file
14
examples/cronJob-support/ks-configMap.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# ------------------- Kubescape User/Customer ID ------------------- #
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"customerGUID": "<ID>",
|
||||
"clusterName": "<cluster name>"
|
||||
}
|
||||
32
examples/cronJob-support/ks-cronJob-non-submit.yaml
Normal file
32
examples/cronJob-support/ks-cronJob-non-submit.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
spec:
|
||||
# ┌────────────────── timezone (optional)
|
||||
# | ┌───────────── minute (0 - 59)
|
||||
# | │ ┌───────────── hour (0 - 23)
|
||||
# | │ │ ┌───────────── day of the month (1 - 31)
|
||||
# | │ │ │ ┌───────────── month (1 - 12)
|
||||
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
|
||||
# | │ │ │ │ │ 7 is also Sunday on some systems)
|
||||
# | │ │ │ │ │
|
||||
# | │ │ │ │ │
|
||||
# CRON_TZ=UTC * * * * *
|
||||
schedule: "0 0 1 * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kubescape
|
||||
image: quay.io/armosec/kubescape:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh","-c"]
|
||||
args:
|
||||
- kubescape scan framework nsa
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: kubescape-discovery
|
||||
40
examples/cronJob-support/ks-cronJob-submit.yaml
Normal file
40
examples/cronJob-support/ks-cronJob-submit.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
spec:
|
||||
# ┌────────────────── timezone (optional)
|
||||
# | ┌───────────── minute (0 - 59)
|
||||
# | │ ┌───────────── hour (0 - 23)
|
||||
# | │ │ ┌───────────── day of the month (1 - 31)
|
||||
# | │ │ │ ┌───────────── month (1 - 12)
|
||||
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
|
||||
# | │ │ │ │ │ 7 is also Sunday on some systems)
|
||||
# | │ │ │ │ │
|
||||
# | │ │ │ │ │
|
||||
# CRON_TZ=UTC * * * * *
|
||||
schedule: "0 0 1 * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kubescape
|
||||
image: quay.io/armosec/kubescape:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh","-c"]
|
||||
args:
|
||||
- kubescape scan framework nsa --submit
|
||||
volumeMounts:
|
||||
- name: kubescape-config-volume
|
||||
mountPath: /root/.kubescape/config.json
|
||||
subPath: config.json
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: kubescape-discovery
|
||||
volumes:
|
||||
- name: kubescape-config-volume
|
||||
configMap:
|
||||
name: kubescape
|
||||
7
examples/cronJob-support/ks-namespace.yaml
Normal file
7
examples/cronJob-support/ks-namespace.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# ------------------- Kubescape User/Customer ID ------------------- #
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
61
examples/cronJob-support/ks-serviceAccount.yaml
Normal file
61
examples/cronJob-support/ks-serviceAccount.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
# ------------------- Kubescape Service Account ------------------- #
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: kubescape
|
||||
name: kubescape-discovery
|
||||
namespace: kubescape
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Role & Role Binding ------------------- #
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role
|
||||
namespace: kubescape
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kubescape-discovery-binding
|
||||
namespace: kubescape
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kubescape-discovery-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Cluster Role & Cluster Role Binding ------------------- #
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-clusterroles
|
||||
# "namespace" omitted since ClusterRoles are not namespaced
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kubescape-discovery-clusterroles
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
namespace: kubescape
|
||||
BIN
examples/cronJob-support/screenshots/account.png
Normal file
BIN
examples/cronJob-support/screenshots/account.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
examples/cronJob-support/screenshots/add-cluster.png
Normal file
BIN
examples/cronJob-support/screenshots/add-cluster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
@@ -1,3 +1,10 @@
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
#
|
||||
# This file is DEPRECATE, please navigate to the official docs ->
|
||||
# https://github.com/armosec/kubescape/tree/master/examples/cronJob-support/README.md
|
||||
#
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Service Account ------------------- #
|
||||
apiVersion: v1
|
||||
|
||||
4
go.mod
4
go.mod
@@ -4,8 +4,8 @@ go 1.17
|
||||
|
||||
require (
|
||||
github.com/armosec/armoapi-go v0.0.8
|
||||
github.com/armosec/k8s-interface v0.0.5
|
||||
github.com/armosec/opa-utils v0.0.13
|
||||
github.com/armosec/k8s-interface v0.0.8
|
||||
github.com/armosec/opa-utils v0.0.18
|
||||
github.com/armosec/utils-go v0.0.3
|
||||
github.com/briandowns/spinner v1.16.0
|
||||
github.com/enescakir/emoji v1.0.0
|
||||
|
||||
7
go.sum
7
go.sum
@@ -87,10 +87,11 @@ github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qm
|
||||
github.com/armosec/armoapi-go v0.0.7/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/armoapi-go v0.0.8 h1:JPa9rZynuE2RucamDh6dsy/sjCScmWDsyt1zagJFCDo=
|
||||
github.com/armosec/armoapi-go v0.0.8/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/k8s-interface v0.0.5 h1:DWQXZNMSsYQeLQ6xpB21ueFMR9oFnz28iWQTNn31TAk=
|
||||
github.com/armosec/k8s-interface v0.0.5/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/opa-utils v0.0.13 h1:QkmmYX0lzC7ZNGetyD8ysRKQHgJhjMfvRUW2cp+hz2o=
|
||||
github.com/armosec/opa-utils v0.0.13/go.mod h1:E0mFTVx+4BYAVvO2hxWnIniv/IZIogRCak8BkKd7KK4=
|
||||
github.com/armosec/k8s-interface v0.0.8 h1:Eo3Qen4yFXxzVem49FNeij2ckyzHSAJ0w6PZMaSEIm8=
|
||||
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/opa-utils v0.0.18 h1:1hL5v2KCD8yStuwzul+gq1zg9+RCV9N3kHoRepKnrg0=
|
||||
github.com/armosec/opa-utils v0.0.18/go.mod h1:E0mFTVx+4BYAVvO2hxWnIniv/IZIogRCak8BkKd7KK4=
|
||||
github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
|
||||
github.com/armosec/utils-go v0.0.3 h1:uyQI676yRciQM0sSN9uPoqHkbspTxHO0kmzXhBeE/xU=
|
||||
github.com/armosec/utils-go v0.0.3/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
|
||||
|
||||
14
main.go
14
main.go
@@ -5,19 +5,25 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/clihandler/cmd"
|
||||
pkgutils "github.com/armosec/utils-go/utils"
|
||||
)
|
||||
|
||||
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
|
||||
|
||||
func main() {
|
||||
CheckLatestVersion()
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
func CheckLatestVersion() {
|
||||
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) {
|
||||
return
|
||||
}
|
||||
latest, err := cmd.GetLatestVersion()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
} else if latest != cmd.BuildNumber {
|
||||
if err != nil || latest == "unknown" {
|
||||
return
|
||||
}
|
||||
if latest != cmd.BuildNumber {
|
||||
fmt.Println("Warning: You are not updated to the latest release: " + latest)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/exceptions"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/score"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
|
||||
@@ -226,17 +225,6 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) updateScore() {
|
||||
|
||||
if !k8sinterface.ConnectedToCluster {
|
||||
return
|
||||
}
|
||||
|
||||
// calculate score
|
||||
s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath)
|
||||
s.Calculate(opap.PostureReport.FrameworkReports)
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) updateResults() {
|
||||
for f := range opap.PostureReport.FrameworkReports {
|
||||
// set exceptions
|
||||
|
||||
@@ -4,30 +4,25 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
)
|
||||
|
||||
var supportedFrameworks = []reporthandling.PolicyIdentifier{
|
||||
{Kind: "Framework", Name: "nsa"},
|
||||
{Kind: "Framework", Name: "mitre"},
|
||||
}
|
||||
|
||||
// PolicyHandler -
|
||||
type PolicyHandler struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
resourceHandler resourcehandler.IResourceHandler
|
||||
// we are listening on this chan in opaprocessor/processorhandler.go/ProcessRulesListenner func
|
||||
processPolicy *chan *cautils.OPASessionObj
|
||||
getters *cautils.Getters
|
||||
}
|
||||
|
||||
// CreatePolicyHandler Create ws-handler obj
|
||||
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, k8s *k8sinterface.KubernetesApi) *PolicyHandler {
|
||||
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, resourceHandler resourcehandler.IResourceHandler) *PolicyHandler {
|
||||
return &PolicyHandler{
|
||||
k8s: k8s,
|
||||
processPolicy: processPolicy,
|
||||
resourceHandler: resourceHandler,
|
||||
processPolicy: processPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,16 +77,7 @@ func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.Pol
|
||||
}
|
||||
|
||||
func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
|
||||
var k8sResources *cautils.K8SResources
|
||||
var err error
|
||||
if k8sinterface.ConnectedToCluster { // TODO - use interface
|
||||
if opaSessionObj.PostureReport.ClusterAPIServerInfo, err = policyHandler.k8s.KubernetesClient.Discovery().ServerVersion(); err != nil {
|
||||
cautils.ErrorDisplay(fmt.Sprintf("Failed to discover API server inforamtion: %v", err))
|
||||
}
|
||||
k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, ¬ification.Designators, scanInfo.ExcludedNamespaces)
|
||||
} else {
|
||||
k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
|
||||
}
|
||||
|
||||
return k8sResources, err
|
||||
opaSessionObj.PostureReport.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
|
||||
return policyHandler.resourceHandler.GetResources(opaSessionObj.Frameworks, ¬ification.Designators)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporth
|
||||
frameworks := []reporthandling.Framework{}
|
||||
exceptionPolicies := []armotypes.PostureExceptionPolicy{}
|
||||
// Get - cacli opa get
|
||||
for _, rule := range notification.Rules {
|
||||
switch rule.Kind {
|
||||
case reporthandling.KindFramework:
|
||||
rule := GetScanKind(notification)
|
||||
|
||||
switch rule.Kind {
|
||||
case reporthandling.KindFramework:
|
||||
for _, rule := range notification.Rules {
|
||||
receivedFramework, recExceptionPolicies, err := policyHandler.getFrameworkPolicies(rule.Name)
|
||||
if receivedFramework != nil {
|
||||
frameworks = append(frameworks, *receivedFramework)
|
||||
@@ -29,27 +31,32 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporth
|
||||
}
|
||||
return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error())
|
||||
}
|
||||
case reporthandling.KindControl:
|
||||
receivedControls, recExceptionPolicies, err := policyHandler.getControl(rule.Name)
|
||||
if receivedControls != nil {
|
||||
f := reporthandling.Framework{
|
||||
Controls: receivedControls,
|
||||
}
|
||||
frameworks = append(frameworks, f)
|
||||
}
|
||||
case reporthandling.KindControl:
|
||||
f := reporthandling.Framework{}
|
||||
var receivedControl *reporthandling.Control
|
||||
var recExceptionPolicies []armotypes.PostureExceptionPolicy
|
||||
var err error
|
||||
for _, rule := range notification.Rules {
|
||||
receivedControl, recExceptionPolicies, err = policyHandler.getControl(rule.Name)
|
||||
if receivedControl != nil {
|
||||
f.Controls = append(f.Controls, *receivedControl)
|
||||
if recExceptionPolicies != nil {
|
||||
exceptionPolicies = append(exceptionPolicies, recExceptionPolicies...)
|
||||
}
|
||||
|
||||
} else if err != nil {
|
||||
if strings.Contains(err.Error(), "unsupported protocol scheme") {
|
||||
err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag")
|
||||
}
|
||||
return nil, nil, fmt.Errorf("error: %s", err.Error())
|
||||
}
|
||||
// TODO: add case for control from file
|
||||
default:
|
||||
err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework)
|
||||
errs = fmt.Errorf("%s", err.Error())
|
||||
}
|
||||
frameworks = append(frameworks, f)
|
||||
// TODO: add case for control from file
|
||||
default:
|
||||
err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework)
|
||||
errs = fmt.Errorf("%s", err.Error())
|
||||
}
|
||||
return frameworks, exceptionPolicies, errs
|
||||
}
|
||||
@@ -68,24 +75,30 @@ func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*re
|
||||
return receivedFramework, receivedException, nil
|
||||
}
|
||||
|
||||
func GetScanKind(notification *reporthandling.PolicyNotification) *reporthandling.PolicyIdentifier {
|
||||
if len(notification.Rules) > 0 {
|
||||
return ¬ification.Rules[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get control by name
|
||||
func (policyHandler *PolicyHandler) getControl(policyName string) ([]reporthandling.Control, []armotypes.PostureExceptionPolicy, error) {
|
||||
func (policyHandler *PolicyHandler) getControl(policyName string) (*reporthandling.Control, []armotypes.PostureExceptionPolicy, error) {
|
||||
|
||||
controls := []reporthandling.Control{}
|
||||
|
||||
control, err := policyHandler.getters.PolicyGetter.GetControl(policyName)
|
||||
control := &reporthandling.Control{}
|
||||
var err error
|
||||
control, err = policyHandler.getters.PolicyGetter.GetControl(policyName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return control, nil, err
|
||||
}
|
||||
if control == nil {
|
||||
return nil, nil, fmt.Errorf("control not found")
|
||||
}
|
||||
controls = append(controls, *control)
|
||||
// if control == nil {
|
||||
// return control, nil, fmt.Errorf("control not found")
|
||||
// }
|
||||
|
||||
exceptions, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName)
|
||||
if err != nil {
|
||||
return controls, nil, err
|
||||
return control, nil, err
|
||||
}
|
||||
|
||||
return controls, exceptions, nil
|
||||
return control, exceptions, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
@@ -29,11 +31,22 @@ const (
|
||||
JSON_FILE_FORMAT FileFormat = "json"
|
||||
)
|
||||
|
||||
func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Framework, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
|
||||
// FileResourceHandler handle resources from files and URLs
|
||||
type FileResourceHandler struct {
|
||||
inputPatterns []string
|
||||
}
|
||||
|
||||
func NewFileResourceHandler(inputPatterns []string) *FileResourceHandler {
|
||||
return &FileResourceHandler{
|
||||
inputPatterns: inputPatterns,
|
||||
}
|
||||
}
|
||||
|
||||
func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
|
||||
workloads := []k8sinterface.IWorkload{}
|
||||
|
||||
// load resource from local file system
|
||||
w, err := loadResourcesFromFiles(scanInfo.InputPatterns)
|
||||
w, err := loadResourcesFromFiles(fileHandler.inputPatterns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -42,7 +55,7 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
|
||||
}
|
||||
|
||||
// load resources from url
|
||||
w, err = loadResourcesFromUrl(scanInfo.InputPatterns)
|
||||
w, err = loadResourcesFromUrl(fileHandler.inputPatterns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,7 +72,7 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
|
||||
|
||||
// build resources map
|
||||
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads>
|
||||
k8sResources := setResourceMap(frameworks)
|
||||
k8sResources := setResourceMap(frameworks) // TODO - support designators
|
||||
|
||||
// save only relevant resources
|
||||
for i := range allResources {
|
||||
@@ -72,6 +85,10 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
|
||||
|
||||
}
|
||||
|
||||
func (fileHandler *FileResourceHandler) GetClusterAPIServerInfo() *version.Info {
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadResourcesFromFiles(inputPatterns []string) ([]k8sinterface.IWorkload, error) {
|
||||
files, errs := listFiles(inputPatterns)
|
||||
if len(errs) > 0 {
|
||||
@@ -1,4 +1,4 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,6 +1,7 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -15,12 +16,23 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
k8slabels "k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
const SelectAllResources = "*"
|
||||
type K8sResourceHandler struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
excludedNamespaces string // excluded namespaces (separated by comma)
|
||||
}
|
||||
|
||||
func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator, excludedNamespaces string) (*cautils.K8SResources, error) {
|
||||
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, excludedNamespaces string) *K8sResourceHandler {
|
||||
return &K8sResourceHandler{
|
||||
k8s: k8s,
|
||||
excludedNamespaces: excludedNamespaces,
|
||||
}
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
|
||||
// get k8s resources
|
||||
cautils.ProgressTextDisplay("Accessing Kubernetes objects")
|
||||
|
||||
@@ -31,7 +43,7 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.
|
||||
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
|
||||
|
||||
// pull k8s recourses
|
||||
if err := policyHandler.pullResources(k8sResourcesMap, namespace, labels, excludedNamespaces); err != nil {
|
||||
if err := k8sHandler.pullResources(k8sResourcesMap, namespace, labels, k8sHandler.excludedNamespaces); err != nil {
|
||||
return k8sResourcesMap, err
|
||||
}
|
||||
|
||||
@@ -39,13 +51,21 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.
|
||||
return k8sResourcesMap, nil
|
||||
}
|
||||
|
||||
func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
|
||||
func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
|
||||
clusterAPIServerInfo, err := k8sHandler.k8s.KubernetesClient.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
cautils.ErrorDisplay(fmt.Sprintf("Failed to discover API server information: %v", err))
|
||||
return nil
|
||||
}
|
||||
return clusterAPIServerInfo
|
||||
}
|
||||
func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
|
||||
|
||||
var errs error
|
||||
for groupResource := range *k8sResources {
|
||||
apiGroup, apiVersion, resource := k8sinterface.StringToResourceGroup(groupResource)
|
||||
gvr := schema.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: resource}
|
||||
result, err := policyHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
|
||||
result, err := k8sHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
|
||||
if err != nil {
|
||||
// handle error
|
||||
if errs == nil {
|
||||
@@ -61,7 +81,7 @@ func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResou
|
||||
return errs
|
||||
}
|
||||
|
||||
func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
|
||||
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
|
||||
|
||||
// set labels
|
||||
listOptions := metav1.ListOptions{}
|
||||
@@ -76,13 +96,13 @@ func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVer
|
||||
// set dynamic object
|
||||
var clientResource dynamic.ResourceInterface
|
||||
if namespace != "" && k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
|
||||
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
|
||||
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
|
||||
} else {
|
||||
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource)
|
||||
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource)
|
||||
}
|
||||
|
||||
// list resources
|
||||
result, err := clientResource.List(policyHandler.k8s.Context, listOptions)
|
||||
result, err := clientResource.List(context.Background(), listOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get resource: %v, namespace: %s, labelSelector: %v, reason: %s", resource, namespace, listOptions.LabelSelector, err.Error())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
@@ -1,4 +1,4 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
@@ -1,4 +1,4 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
13
resourcehandler/resourceshandler.go
Normal file
13
resourcehandler/resourceshandler.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
type IResourceHandler interface {
|
||||
GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error)
|
||||
GetClusterAPIServerInfo() *version.Info
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package policyhandler
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
42
resultshandling/printer/jsonprinter.go
Normal file
42
resultshandling/printer/jsonprinter.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
)
|
||||
|
||||
type JsonPrinter struct {
|
||||
writer *os.File
|
||||
}
|
||||
|
||||
func NewJsonPrinter() *JsonPrinter {
|
||||
return &JsonPrinter{}
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
|
||||
jsonPrinter.writer = getWriter(outputFile)
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) Score(score float32) {
|
||||
fmt.Printf("\nFinal score: %d", int(score*100))
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
var postureReportStr []byte
|
||||
var err error
|
||||
|
||||
if len(opaSessionObj.PostureReport.FrameworkReports) == 1 {
|
||||
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
|
||||
} else {
|
||||
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Failed to convert posture report object!")
|
||||
os.Exit(1)
|
||||
}
|
||||
jsonPrinter.writer.Write(postureReportStr)
|
||||
}
|
||||
@@ -3,10 +3,42 @@ package printer
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type JunitPrinter struct {
|
||||
writer *os.File
|
||||
}
|
||||
|
||||
func NewJunitPrinter() *JunitPrinter {
|
||||
return &JunitPrinter{}
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
|
||||
junitPrinter.writer = getWriter(outputFile)
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) Score(score float32) {
|
||||
fmt.Printf("\nFinal score: %d", int(score*100))
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to convert posture report object!")
|
||||
os.Exit(1)
|
||||
}
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to convert posture report object!")
|
||||
os.Exit(1)
|
||||
}
|
||||
junitPrinter.writer.Write(postureReportStr)
|
||||
}
|
||||
|
||||
type JUnitTestSuites struct {
|
||||
XMLName xml.Name `xml:"testsuites"`
|
||||
Suites []JUnitTestSuite `xml:"testsuite"`
|
||||
@@ -74,9 +106,9 @@ func convertPostureReportToJunitResult(postureResult *reporthandling.PostureRepo
|
||||
testCase.Time = "0"
|
||||
if 0 < len(controlReports.RuleReports[0].RuleResponses) {
|
||||
|
||||
testCase.Resources = framework.GetNumberOfResources()
|
||||
testCase.Excluded = framework.GetNumberOfWarningResources()
|
||||
testCase.Failed = framework.GetNumberOfFailedResources()
|
||||
testCase.Resources = controlReports.GetNumberOfResources()
|
||||
testCase.Excluded = controlReports.GetNumberOfWarningResources()
|
||||
testCase.Failed = controlReports.GetNumberOfFailedResources()
|
||||
failure := JUnitFailure{}
|
||||
failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
|
||||
for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
|
||||
|
||||
239
resultshandling/printer/prettyprinter.go
Normal file
239
resultshandling/printer/prettyprinter.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
type PrettyPrinter struct {
|
||||
writer *os.File
|
||||
summary Summary
|
||||
sortedControlNames []string
|
||||
frameworkSummary ControlSummary
|
||||
}
|
||||
|
||||
func NewPrettyPrinter() *PrettyPrinter {
|
||||
return &PrettyPrinter{
|
||||
summary: NewSummary(),
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes empty printer for new table
|
||||
func (printer *PrettyPrinter) init() *PrettyPrinter {
|
||||
printer.frameworkSummary = ControlSummary{}
|
||||
printer.summary = Summary{}
|
||||
printer.sortedControlNames = []string{}
|
||||
return printer
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
// score := calculatePostureScore(opaSessionObj.PostureReport)
|
||||
for _, report := range opaSessionObj.PostureReport.FrameworkReports {
|
||||
// Print summary table together for control scan
|
||||
if report.Name != "" {
|
||||
printer = printer.init()
|
||||
}
|
||||
printer.summarySetup(report)
|
||||
printer.printResults()
|
||||
printer.printSummaryTable(report.Name)
|
||||
}
|
||||
|
||||
// return score
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) SetWriter(outputFile string) {
|
||||
printer.writer = getWriter(outputFile)
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) Score(score float32) {
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport) {
|
||||
printer.frameworkSummary = ControlSummary{
|
||||
TotalResources: fr.GetNumberOfResources(),
|
||||
TotalFailed: fr.GetNumberOfFailedResources(),
|
||||
TotalWarnign: fr.GetNumberOfWarningResources(),
|
||||
}
|
||||
for _, cr := range fr.ControlReports {
|
||||
if len(cr.RuleReports) == 0 {
|
||||
continue
|
||||
}
|
||||
workloadsSummary := listResultSummary(cr.RuleReports)
|
||||
|
||||
printer.summary[cr.Name] = ControlSummary{
|
||||
TotalResources: cr.GetNumberOfResources(),
|
||||
TotalFailed: cr.GetNumberOfFailedResources(),
|
||||
TotalWarnign: cr.GetNumberOfWarningResources(),
|
||||
FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed),
|
||||
ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude),
|
||||
Description: cr.Description,
|
||||
Remediation: cr.Remediation,
|
||||
ListInputKinds: cr.ListControlsInputKinds(),
|
||||
}
|
||||
}
|
||||
printer.sortedControlNames = printer.getSortedControlsNames()
|
||||
}
|
||||
func (printer *PrettyPrinter) printResults() {
|
||||
for i := 0; i < len(printer.sortedControlNames); i++ {
|
||||
controlSummary := printer.summary[printer.sortedControlNames[i]]
|
||||
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
|
||||
printer.printResources(&controlSummary)
|
||||
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
|
||||
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) printSummary(controlName string, controlSummary *ControlSummary) {
|
||||
cautils.SimpleDisplay(printer.writer, "Summary - ")
|
||||
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarnign)
|
||||
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarnign)
|
||||
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
|
||||
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
|
||||
if controlSummary.TotalFailed > 0 {
|
||||
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
|
||||
}
|
||||
cautils.DescriptionDisplay(printer.writer, "\n")
|
||||
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) printTitle(controlName string, controlSummary *ControlSummary) {
|
||||
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
|
||||
if controlSummary.TotalResources == 0 {
|
||||
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
|
||||
} else if controlSummary.TotalFailed != 0 {
|
||||
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
|
||||
} else if controlSummary.TotalWarnign != 0 {
|
||||
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
|
||||
} else {
|
||||
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
|
||||
}
|
||||
|
||||
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
|
||||
|
||||
}
|
||||
func (printer *PrettyPrinter) printResources(controlSummary *ControlSummary) {
|
||||
|
||||
if len(controlSummary.FailedWorkloads) > 0 {
|
||||
cautils.FailureDisplay(printer.writer, "Failed:\n")
|
||||
printer.printGroupedResources(controlSummary.FailedWorkloads)
|
||||
}
|
||||
if len(controlSummary.ExcludedWorkloads) > 0 {
|
||||
cautils.WarningDisplay(printer.writer, "Excluded:\n")
|
||||
printer.printGroupedResources(controlSummary.ExcludedWorkloads)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
|
||||
|
||||
indent := INDENT
|
||||
|
||||
for ns, rsc := range workloads {
|
||||
preIndent := indent
|
||||
if ns != "" {
|
||||
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
|
||||
}
|
||||
preIndent2 := indent
|
||||
for r := range rsc {
|
||||
indent += indent
|
||||
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
|
||||
indent = preIndent2
|
||||
}
|
||||
indent = preIndent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func generateRow(control string, cs ControlSummary) []string {
|
||||
row := []string{control}
|
||||
row = append(row, cs.ToSlice()...)
|
||||
if cs.TotalResources != 0 {
|
||||
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
|
||||
} else {
|
||||
row = append(row, EmptyPercentage)
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func generateHeader() []string {
|
||||
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% success"}
|
||||
}
|
||||
|
||||
func percentage(big, small int) int {
|
||||
if big == 0 {
|
||||
if small == 0 {
|
||||
return 100
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return int(float64(float64(big-small)/float64(big)) * 100)
|
||||
}
|
||||
func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string {
|
||||
// Control name | # failed resources | all resources | % success
|
||||
row := []string{}
|
||||
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
|
||||
row = append(row, fmt.Sprintf("%d", sumFailed))
|
||||
row = append(row, fmt.Sprintf("%d", sumWarning))
|
||||
row = append(row, fmt.Sprintf("%d", sumTotal))
|
||||
if sumTotal != 0 {
|
||||
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
|
||||
} else {
|
||||
row = append(row, EmptyPercentage)
|
||||
}
|
||||
return row
|
||||
}
|
||||
func (printer *PrettyPrinter) printSummaryTable(framework string) {
|
||||
// For control scan framework will be nil
|
||||
printer.printFramework(framework)
|
||||
|
||||
summaryTable := tablewriter.NewWriter(printer.writer)
|
||||
summaryTable.SetAutoWrapText(false)
|
||||
summaryTable.SetHeader(generateHeader())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
summaryTable.SetColumnAlignment(alignments)
|
||||
|
||||
for i := 0; i < len(printer.sortedControlNames); i++ {
|
||||
controlSummary := printer.summary[printer.sortedControlNames[i]]
|
||||
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
|
||||
}
|
||||
summaryTable.SetFooter(generateFooter(len(printer.summary), printer.frameworkSummary.TotalFailed, printer.frameworkSummary.TotalWarnign, printer.frameworkSummary.TotalResources))
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) printFramework(framework string) {
|
||||
if framework != "" {
|
||||
cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("%s FRAMEWORK\n", framework))
|
||||
}
|
||||
}
|
||||
|
||||
func (printer *PrettyPrinter) getSortedControlsNames() []string {
|
||||
controlNames := make([]string, 0, len(printer.summary))
|
||||
for k := range printer.summary {
|
||||
controlNames = append(controlNames, k)
|
||||
}
|
||||
sort.Strings(controlNames)
|
||||
return controlNames
|
||||
}
|
||||
|
||||
func getWriter(outputFile string) *os.File {
|
||||
os.Remove(outputFile)
|
||||
if outputFile != "" {
|
||||
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file")
|
||||
return os.Stdout
|
||||
}
|
||||
return f
|
||||
}
|
||||
return os.Stdout
|
||||
|
||||
}
|
||||
@@ -1,17 +1,7 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var INDENT = " "
|
||||
@@ -19,253 +9,24 @@ var INDENT = " "
|
||||
const EmptyPercentage = "NaN"
|
||||
|
||||
const (
|
||||
PrettyPrinter string = "pretty-printer"
|
||||
JsonPrinter string = "json"
|
||||
PrettyFormat string = "pretty-printer"
|
||||
JsonFormat string = "json"
|
||||
JunitResultPrinter string = "junit"
|
||||
)
|
||||
|
||||
type Printer struct {
|
||||
writer *os.File
|
||||
summary Summary
|
||||
sortedControlNames []string
|
||||
printerType string
|
||||
frameworkSummary ControlSummary
|
||||
type IPrinter interface {
|
||||
ActionPrint(opaSessionObj *cautils.OPASessionObj)
|
||||
SetWriter(outputFile string)
|
||||
Score(score float32)
|
||||
}
|
||||
|
||||
func NewPrinter(printerType, outputFile string) *Printer {
|
||||
return &Printer{
|
||||
summary: NewSummary(),
|
||||
writer: getWriter(outputFile),
|
||||
printerType: printerType,
|
||||
func GetPrinter(printFormat string) IPrinter {
|
||||
switch printFormat {
|
||||
case JsonFormat:
|
||||
return NewJsonPrinter()
|
||||
case JunitResultPrinter:
|
||||
return NewJunitPrinter()
|
||||
default:
|
||||
return NewPrettyPrinter()
|
||||
}
|
||||
}
|
||||
|
||||
func calculatePostureScore(postureReport *reporthandling.PostureReport) float32 {
|
||||
totalResources := 0
|
||||
totalFailed := 0
|
||||
for _, frameworkReport := range postureReport.FrameworkReports {
|
||||
totalFailed += frameworkReport.GetNumberOfFailedResources()
|
||||
totalResources += frameworkReport.GetNumberOfResources()
|
||||
}
|
||||
if totalResources == 0 {
|
||||
return float32(0)
|
||||
}
|
||||
return (float32(totalResources) - float32(totalFailed)) / float32(totalResources)
|
||||
}
|
||||
|
||||
func (printer *Printer) ActionPrint(opaSessionObj *cautils.OPASessionObj) float32 {
|
||||
score := calculatePostureScore(opaSessionObj.PostureReport)
|
||||
|
||||
if printer.printerType == PrettyPrinter {
|
||||
printer.SummarySetup(opaSessionObj.PostureReport)
|
||||
printer.PrintResults()
|
||||
printer.PrintSummaryTable()
|
||||
} else if printer.printerType == JsonPrinter {
|
||||
postureReportStr, err := json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
|
||||
if err != nil {
|
||||
fmt.Println("Failed to convert posture report object!")
|
||||
os.Exit(1)
|
||||
}
|
||||
printer.writer.Write(postureReportStr)
|
||||
fmt.Printf("\nFinal score: %d\n", int(score*100))
|
||||
} else if printer.printerType == JunitResultPrinter {
|
||||
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to convert posture report object!")
|
||||
os.Exit(1)
|
||||
}
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to convert posture report object!")
|
||||
os.Exit(1)
|
||||
}
|
||||
printer.writer.Write(postureReportStr)
|
||||
fmt.Printf("\nFinal score: %d\n", int(score*100))
|
||||
} else if !cautils.IsSilent() {
|
||||
fmt.Println("unknown output printer")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func (printer *Printer) SummarySetup(postureReport *reporthandling.PostureReport) {
|
||||
for _, fr := range postureReport.FrameworkReports {
|
||||
printer.frameworkSummary = ControlSummary{
|
||||
TotalResources: fr.GetNumberOfResources(),
|
||||
TotalFailed: fr.GetNumberOfFailedResources(),
|
||||
TotalWarnign: fr.GetNumberOfWarningResources(),
|
||||
}
|
||||
for _, cr := range fr.ControlReports {
|
||||
if len(cr.RuleReports) == 0 {
|
||||
continue
|
||||
}
|
||||
workloadsSummary := listResultSummary(cr.RuleReports)
|
||||
|
||||
printer.summary[cr.Name] = ControlSummary{
|
||||
TotalResources: cr.GetNumberOfResources(),
|
||||
TotalFailed: cr.GetNumberOfFailedResources(),
|
||||
TotalWarnign: cr.GetNumberOfWarningResources(),
|
||||
FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed),
|
||||
ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude),
|
||||
Description: cr.Description,
|
||||
Remediation: cr.Remediation,
|
||||
ListInputKinds: cr.ListControlsInputKinds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
printer.sortedControlNames = printer.getSortedControlsNames()
|
||||
}
|
||||
func (printer *Printer) PrintResults() {
|
||||
for i := 0; i < len(printer.sortedControlNames); i++ {
|
||||
controlSummary := printer.summary[printer.sortedControlNames[i]]
|
||||
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
|
||||
printer.printResources(&controlSummary)
|
||||
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
|
||||
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (printer *Printer) printSummary(controlName string, controlSummary *ControlSummary) {
|
||||
cautils.SimpleDisplay(printer.writer, "Summary - ")
|
||||
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarnign)
|
||||
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarnign)
|
||||
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
|
||||
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
|
||||
if controlSummary.TotalFailed > 0 {
|
||||
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
|
||||
}
|
||||
cautils.DescriptionDisplay(printer.writer, "\n")
|
||||
|
||||
}
|
||||
|
||||
func (printer *Printer) printTitle(controlName string, controlSummary *ControlSummary) {
|
||||
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
|
||||
if controlSummary.TotalResources == 0 {
|
||||
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
|
||||
} else if controlSummary.TotalFailed != 0 {
|
||||
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
|
||||
} else if controlSummary.TotalWarnign != 0 {
|
||||
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
|
||||
} else {
|
||||
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
|
||||
}
|
||||
|
||||
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
|
||||
|
||||
}
|
||||
func (printer *Printer) printResources(controlSummary *ControlSummary) {
|
||||
|
||||
if len(controlSummary.FailedWorkloads) > 0 {
|
||||
cautils.FailureDisplay(printer.writer, "Failed:\n")
|
||||
printer.printGroupedResources(controlSummary.FailedWorkloads)
|
||||
}
|
||||
if len(controlSummary.ExcludedWorkloads) > 0 {
|
||||
cautils.WarningDisplay(printer.writer, "Excluded:\n")
|
||||
printer.printGroupedResources(controlSummary.ExcludedWorkloads)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (printer *Printer) printGroupedResources(workloads map[string][]WorkloadSummary) {
|
||||
|
||||
indent := INDENT
|
||||
|
||||
for ns, rsc := range workloads {
|
||||
preIndent := indent
|
||||
if ns != "" {
|
||||
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
|
||||
}
|
||||
preIndent2 := indent
|
||||
for r := range rsc {
|
||||
indent += indent
|
||||
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
|
||||
indent = preIndent2
|
||||
}
|
||||
indent = preIndent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (printer *Printer) PrintUrl(url string) {
|
||||
cautils.InfoTextDisplay(printer.writer, url)
|
||||
}
|
||||
|
||||
func generateRow(control string, cs ControlSummary) []string {
|
||||
row := []string{control}
|
||||
row = append(row, cs.ToSlice()...)
|
||||
if cs.TotalResources != 0 {
|
||||
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
|
||||
} else {
|
||||
row = append(row, EmptyPercentage)
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func generateHeader() []string {
|
||||
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% success"}
|
||||
}
|
||||
|
||||
func percentage(big, small int) int {
|
||||
if big == 0 {
|
||||
if small == 0 {
|
||||
return 100
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return int(float64(float64(big-small)/float64(big)) * 100)
|
||||
}
|
||||
func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string {
|
||||
// Control name | # failed resources | all resources | % success
|
||||
row := []string{}
|
||||
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
|
||||
row = append(row, fmt.Sprintf("%d", sumFailed))
|
||||
row = append(row, fmt.Sprintf("%d", sumWarning))
|
||||
row = append(row, fmt.Sprintf("%d", sumTotal))
|
||||
if sumTotal != 0 {
|
||||
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
|
||||
} else {
|
||||
row = append(row, EmptyPercentage)
|
||||
}
|
||||
return row
|
||||
}
|
||||
func (printer *Printer) PrintSummaryTable() {
|
||||
summaryTable := tablewriter.NewWriter(printer.writer)
|
||||
summaryTable.SetAutoWrapText(false)
|
||||
summaryTable.SetHeader(generateHeader())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
summaryTable.SetColumnAlignment(alignments)
|
||||
|
||||
for i := 0; i < len(printer.sortedControlNames); i++ {
|
||||
controlSummary := printer.summary[printer.sortedControlNames[i]]
|
||||
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
|
||||
}
|
||||
summaryTable.SetFooter(generateFooter(len(printer.summary), printer.frameworkSummary.TotalFailed, printer.frameworkSummary.TotalWarnign, printer.frameworkSummary.TotalResources))
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func (printer *Printer) getSortedControlsNames() []string {
|
||||
controlNames := make([]string, 0, len(printer.summary))
|
||||
for k := range printer.summary {
|
||||
controlNames = append(controlNames, k)
|
||||
}
|
||||
sort.Strings(controlNames)
|
||||
return controlNames
|
||||
}
|
||||
|
||||
func getWriter(outputFile string) *os.File {
|
||||
os.Remove(outputFile)
|
||||
if outputFile != "" {
|
||||
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file")
|
||||
return os.Stdout
|
||||
}
|
||||
return f
|
||||
}
|
||||
return os.Stdout
|
||||
|
||||
}
|
||||
|
||||
11
resultshandling/printer/silentprinter.go
Normal file
11
resultshandling/printer/silentprinter.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
)
|
||||
|
||||
type SilentPrinter struct {
|
||||
}
|
||||
|
||||
func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
}
|
||||
17
resultshandling/reporter/mockreporter.go
Normal file
17
resultshandling/reporter/mockreporter.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package reporter
|
||||
|
||||
import "github.com/armosec/kubescape/cautils"
|
||||
|
||||
type ReportMock struct {
|
||||
}
|
||||
|
||||
func NewReportMock() *ReportMock {
|
||||
return &ReportMock{}
|
||||
}
|
||||
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) {}
|
||||
|
||||
func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) SetClusterName(clusterName string) {
|
||||
}
|
||||
@@ -5,47 +5,55 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type IReport interface {
|
||||
ActionSendReport(opaSessionObj *cautils.OPASessionObj)
|
||||
SetCustomerGUID(customerGUID string)
|
||||
SetClusterName(clusterName string)
|
||||
}
|
||||
|
||||
type ReportEventReceiver struct {
|
||||
httpClient http.Client
|
||||
host url.URL
|
||||
httpClient http.Client
|
||||
clusterName string
|
||||
customerGUID string
|
||||
}
|
||||
|
||||
func NewReportEventReceiver() *ReportEventReceiver {
|
||||
hostURL := initEventReceiverURL()
|
||||
return &ReportEventReceiver{
|
||||
httpClient: http.Client{},
|
||||
host: *hostURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) ActionSendReportListenner(opaSessionObj *cautils.OPASessionObj) {
|
||||
if cautils.CustomerGUID == "" {
|
||||
return
|
||||
}
|
||||
//Add score
|
||||
|
||||
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) {
|
||||
// Remove data before reporting
|
||||
keepFields := []string{"kind", "apiVersion", "metadata"}
|
||||
keepMetadataFields := []string{"name", "namespace", "labels"}
|
||||
opaSessionObj.PostureReport.RemoveData(keepFields, keepMetadataFields)
|
||||
|
||||
if err := report.Send(opaSessionObj.PostureReport); err != nil {
|
||||
if err := report.send(opaSessionObj.PostureReport); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
func (report *ReportEventReceiver) Send(postureReport *reporthandling.PostureReport) error {
|
||||
|
||||
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
|
||||
report.customerGUID = customerGUID
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) SetClusterName(clusterName string) {
|
||||
report.clusterName = clusterName
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) send(postureReport *reporthandling.PostureReport) error {
|
||||
|
||||
reqBody, err := json.Marshal(*postureReport)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in 'Send' failed to json.Marshal, reason: %v", err)
|
||||
}
|
||||
host := hostToString(&report.host, postureReport.ReportID)
|
||||
host := hostToString(report.initEventReceiverURL(), postureReport.ReportID)
|
||||
|
||||
req, err := http.NewRequest("POST", host, bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
@@ -33,15 +32,15 @@ func httpRespToString(resp *http.Response) (string, error) {
|
||||
return strBuilder.String(), err
|
||||
}
|
||||
|
||||
func initEventReceiverURL() *url.URL {
|
||||
func (report *ReportEventReceiver) initEventReceiverURL() *url.URL {
|
||||
urlObj := url.URL{}
|
||||
|
||||
urlObj.Scheme = "https"
|
||||
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
|
||||
urlObj.Path = "/k8s/postureReport"
|
||||
q := urlObj.Query()
|
||||
q.Add("customerGUID", uuid.FromStringOrNil(cautils.CustomerGUID).String())
|
||||
q.Add("clusterName", cautils.ClusterName)
|
||||
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
|
||||
q.Add("clusterName", report.clusterName)
|
||||
|
||||
urlObj.RawQuery = q.Encode()
|
||||
|
||||
|
||||
@@ -4,15 +4,16 @@ import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type ResultsHandler struct {
|
||||
opaSessionObj *chan *cautils.OPASessionObj
|
||||
reporterObj *reporter.ReportEventReceiver
|
||||
printerObj *printer.Printer
|
||||
reporterObj reporter.IReport
|
||||
printerObj printer.IPrinter
|
||||
}
|
||||
|
||||
func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj *reporter.ReportEventReceiver, printerObj *printer.Printer) *ResultsHandler {
|
||||
func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj reporter.IReport, printerObj printer.IPrinter) *ResultsHandler {
|
||||
return &ResultsHandler{
|
||||
opaSessionObj: opaSessionObj,
|
||||
reporterObj: reporterObj,
|
||||
@@ -20,16 +21,36 @@ func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj *
|
||||
}
|
||||
}
|
||||
|
||||
func (resultsHandler *ResultsHandler) HandleResults(scanInfo cautils.ScanInfo) float32 {
|
||||
func (resultsHandler *ResultsHandler) HandleResults(scanInfo *cautils.ScanInfo) float32 {
|
||||
|
||||
opaSessionObj := <-*resultsHandler.opaSessionObj
|
||||
|
||||
score := resultsHandler.printerObj.ActionPrint(opaSessionObj)
|
||||
resultsHandler.printerObj.ActionPrint(opaSessionObj)
|
||||
|
||||
// Don't send report for control scan
|
||||
if scanInfo.FrameworkScan { // TODO - use interface for ActionSendReportListenner
|
||||
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
|
||||
}
|
||||
resultsHandler.reporterObj.ActionSendReport(opaSessionObj)
|
||||
|
||||
// TODO - get score from table
|
||||
score := CalculatePostureScore(opaSessionObj.PostureReport)
|
||||
resultsHandler.printerObj.Score(score)
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// CalculatePostureScore calculate final score
|
||||
func CalculatePostureScore(postureReport *reporthandling.PostureReport) float32 {
|
||||
lowestScore := float32(100)
|
||||
for _, frameworkReport := range postureReport.FrameworkReports {
|
||||
totalFailed := frameworkReport.GetNumberOfFailedResources()
|
||||
totalResources := frameworkReport.GetNumberOfResources()
|
||||
|
||||
frameworkScore := float32(0)
|
||||
if float32(totalResources) > 0 {
|
||||
frameworkScore = (float32(totalResources) - float32(totalFailed)) / float32(totalResources)
|
||||
}
|
||||
if lowestScore > frameworkScore {
|
||||
lowestScore = frameworkScore
|
||||
}
|
||||
}
|
||||
|
||||
return lowestScore
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user