Compare commits

..

50 Commits

Author SHA1 Message Date
David Wertenteil
2a7c20ea94 report format version 2022-04-07 11:59:19 +03:00
David Wertenteil
bde0dc9a17 convert score to int 2022-04-07 11:00:39 +03:00
David Wertenteil
7d7336ae01 support only one input 2022-04-07 09:53:44 +03:00
David Wertenteil
e5e608324d send file meta 2022-04-06 16:11:57 +03:00
David Wertenteil
569c1444f7 Merge remote-tracking branch 'armosec/dev' 2022-04-06 09:45:32 +03:00
dwertent
dc5ef28324 update output 2022-04-06 09:29:28 +03:00
David Wertenteil
aea6c0eab8 Merge pull request #477 from Daniel-GrunbergerCA/master
Fix error msg for cloud
2022-04-06 09:23:12 +03:00
DanielGrunbergerCA
c80a15d0cf Merge remote-tracking branch 'upstream/dev' 2022-04-05 17:19:11 +03:00
DanielGrunbergerCA
1ddd57aa1d fix error msg for cloud 2022-04-05 17:18:50 +03:00
David Wertenteil
55adb0da6b Merge pull request #474 from Daniel-GrunbergerCA/master
Fix posture control inputs for yaml scan
2022-04-05 16:28:39 +03:00
DanielGrunbergerCA
a716289cc8 go mod 2022-04-05 14:10:11 +03:00
DanielGrunbergerCA
093d71fff4 exit status 1 2022-04-05 14:05:31 +03:00
DanielGrunbergerCA
4fe40e348d Merge branch 'master' of github.com:Daniel-GrunbergerCA/kubescape 2022-04-05 13:40:41 +03:00
DanielGrunbergerCA
29a67b806d Merge remote-tracking branch 'upstream/dev' 2022-04-05 13:39:39 +03:00
DanielGrunbergerCA
0169f42747 Merge remote-tracking branch 'upstream/master' 2022-04-05 13:39:28 +03:00
David Wertenteil
92e100c497 Merge branch 'dev' into master 2022-04-04 13:39:17 +03:00
DanielGrunbergerCA
536fe970f7 fix go mod 2022-04-04 12:31:45 +03:00
DanielGrunbergerCA
85526b06b6 fix control input for yaml scan 2022-04-04 12:27:30 +03:00
dwertent
d9b6c048d5 Merge remote-tracking branch 'armosec/dev' 2022-04-04 12:14:45 +03:00
David Wertenteil
7e46a6529a Merge pull request #473 from Lucifergene/dev
Added Severity Column with colored text
2022-04-04 12:14:35 +03:00
dwertent
2dc5fd80da Merge remote-tracking branch 'armosec/dev' 2022-04-04 12:14:13 +03:00
dwertent
e89cc8ca24 register usesrs download from BE 2022-04-04 12:13:35 +03:00
dwertent
d39aeb0691 update metrics 2022-04-04 11:32:56 +03:00
Lucifergene
da9d98134a Added Severity Column with colored text 2022-04-04 03:46:05 +05:30
David Wertenteil
9992a9a0e4 Merge pull request #472 from falconcode16/master
Improved grammatical mistakes and typos
2022-04-03 10:03:15 +03:00
Rohit Patil
adc8a16e85 Improved grammatical mistakes and typos 2022-04-03 10:40:56 +05:30
dwertent
58b833c18a support yaml scan submit 2022-03-28 09:40:11 +03:00
Rotem Refael
cb424eab00 Merge pull request #461 from armosec/dev
Microservice support
2022-03-27 17:20:35 +03:00
Rotem Refael
9f2e18c3ee Merge pull request #466 from dwertent/master
udpate armo api types
2022-03-27 17:09:52 +03:00
dwertent
b44a73aea5 udpate armo api types 2022-03-27 17:07:17 +03:00
Rotem Refael
9c5759286f Merge pull request #465 from dwertent/master
Update url support
2022-03-27 16:29:01 +03:00
dwertent
74dc714736 use scan ID 2022-03-27 15:51:29 +03:00
dwertent
83751e22cc fixed report sending 2022-03-27 15:37:11 +03:00
dwertent
db5fdd75c4 inserting scan source 2022-03-25 16:08:07 +03:00
dwertent
4be2104d4b adding githubusercontent tests 2022-03-24 12:37:00 +02:00
dwertent
b6bab7618f loading github token from env 2022-03-24 12:15:54 +02:00
dwertent
3e1fda6f3b fixed test 2022-03-24 11:12:28 +02:00
dwertent
8487a031ee fixed url scanning 2022-03-24 09:45:35 +02:00
David Wertenteil
efbb123fce Merge pull request #464 from dwertent/master
update error display
2022-03-23 09:27:58 +02:00
dwertent
5a335d4f1c update error display 2022-03-23 09:05:36 +02:00
dwertent
5770a823d6 update readme 2022-03-23 08:47:24 +02:00
Rotem Refael
52d7be9108 Add kubescape covarage 2022-03-22 17:42:20 +02:00
David Wertenteil
9512b9c6c4 Merge pull request #463 from Daniel-GrunbergerCA/dev
fix json printer
2022-03-22 09:14:59 +02:00
DanielGrunbergerCA
da9ab642ec rm list initializtion 2022-03-22 09:07:55 +02:00
DanielGrunbergerCA
718ca1c7ab fix json printer 2022-03-22 08:55:36 +02:00
DanielGrunbergerCA
ee3742c5a0 update opa-utils version 2022-03-21 15:36:17 +02:00
DanielGrunbergerCA
7eef843a7a fix resources in report 2022-03-21 15:11:26 +02:00
David Wertenteil
b4a8b06f07 Merge pull request #462 from dwertent/master
update get context
2022-03-21 09:09:12 +02:00
DanielGrunbergerCA
6372ce5647 Merge branch 'dev' 2022-03-16 12:13:16 +02:00
Rotem Refael
9733178228 Merge pull request #428 from armosec/dev
Release
2022-03-06 11:51:59 +02:00
65 changed files with 1355 additions and 557 deletions

View File

@@ -38,6 +38,8 @@ jobs:
run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd core && go test -v ./...
- name: Test httphandler pkg

View File

@@ -22,6 +22,8 @@ jobs:
run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd core && go test -v ./...
- name: Test httphandler pkg

View File

@@ -23,6 +23,8 @@ jobs:
run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd core && go test -v ./...
- name: Test httphandler pkg

View File

@@ -1,7 +1,8 @@
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
<br>
![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/armosec/kubescape?label=release&color=blue) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/armosec/kubescape?color=yellow)
[![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape)
Kubescape is a K8s open-source tool providing a multi-cloud K8s single pane of glass, including risk analysis, security compliance, RBAC visualizer and image vulnerabilities scanning.
@@ -11,10 +12,18 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle
</br>
# CLI Interface:
<!-- # Kubescape Coverage
<img src="docs/ksfromcodetodeploy.png">
</br> -->
# Kubescape CLI:
<img src="docs/demo.gif">
<!-- # [Web Interface:](https://portal.armo.cloud/)
</br>
<!-- # Kubescape overview:
<img src="docs/ARMO-header-2022.gif"> -->
# TL;DR
@@ -106,7 +115,7 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan --submit --enable-host-scan
kubescape scan --submit --enable-host-scan --verbose
```
> Read [here](https://hub.armo.cloud/docs/host-sensor) more about the `enable-host-scan` flag
@@ -228,6 +237,7 @@ kubescape download framework nsa --output /path/nsa.json
kubescape scan framework nsa --use-from /path/nsa.json
```
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
[Please follow the instructions here](https://hub.armo.cloud/docs/installation-of-armo-in-cluster)
[helm chart repo](https://github.com/armosec/armo-helm)
@@ -257,6 +267,7 @@ Now you can submit the results to the Kubescape SaaS version -
kubescape submit results path/to/results.json
```
# Integrations
## VS Code Extension
@@ -265,6 +276,7 @@ kubescape submit results path/to/results.json
Scan the YAML files while writing them using the [vs code extension](https://github.com/armosec/vscode-kubescape/blob/master/README.md)
# Under the hood
## Technology

View File

@@ -7,8 +7,9 @@ replace github.com/armosec/kubescape/core => ../core
require (
github.com/armosec/k8s-interface v0.0.68
github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000
github.com/armosec/opa-utils v0.0.120
github.com/armosec/opa-utils v0.0.130
github.com/armosec/rbac-utils v0.0.14
github.com/enescakir/emoji v1.0.0
github.com/google/uuid v1.3.0
github.com/mattn/go-isatty v0.0.14
github.com/spf13/cobra v1.4.0
@@ -51,7 +52,6 @@ require (
github.com/docker/docker v20.10.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/enescakir/emoji v1.0.0 // indirect
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/fatih/color v1.13.0 // indirect
@@ -88,6 +88,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/whilp/git-urls v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k=
github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.130 h1:uP60M0PzmDtLqvsA/jX8BED9/Ava4n2QG7VCkuI+hwI=
github.com/armosec/opa-utils v0.0.130/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
@@ -774,6 +774,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@@ -84,5 +84,8 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
func main() {
ks := NewDefaultKubescapeCommand()
ks.Execute()
err := ks.Execute()
if err != nil {
logger.L().Fatal(err.Error())
}
}

View File

@@ -44,7 +44,7 @@ func initLoggerLevel() {
}
func initCacheDir() {
if rootInfo.CacheDir == getter.DefaultLocalStore {
if rootInfo.CacheDir != getter.DefaultLocalStore {
getter.DefaultLocalStore = rootInfo.CacheDir
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
getter.DefaultLocalStore = cacheDir

View File

@@ -8,8 +8,10 @@ import (
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/kubescape/core/meta"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)
@@ -66,7 +68,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
scanInfo.InputPatterns = []string{args[1]}
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
@@ -88,36 +90,16 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if err != nil {
logger.L().Fatal(err.Error())
}
results.HandleResults()
if err := results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
cautils.SimpleDisplay(os.Stderr, "%s Run with '--verbose' flag for full scan details\n\n", emoji.Detective)
}
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", results.GetRiskScore(), scanInfo.FailThreshold)
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
}
return nil
},
}
}
// func flagValidationControl() {
// if 100 < scanInfo.FailThreshold {
// logger.L().Fatal("bad argument: out of range threshold")
// }
// }
// func setScanForFirstControl(scanInfo, 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(scanInfo, 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
// }

View File

@@ -8,8 +8,10 @@ import (
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/kubescape/core/meta"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)
@@ -27,7 +29,7 @@ var (
# Scan all frameworks
kubescape scan framework all
# Scan kubernetes YAML manifest files
# Scan kubernetes YAML manifest files (single file or glob)
kubescape scan framework nsa *.yaml
Run 'kubescape list frameworks' for the list of supported frameworks
@@ -58,7 +60,9 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationFramework(scanInfo)
if err := flagValidationFramework(scanInfo); err != nil {
return err
}
scanInfo.FrameworkScan = true
var frameworks []string
@@ -74,7 +78,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
}
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
scanInfo.InputPatterns = []string{args[1]}
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
@@ -97,34 +101,27 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
if err != nil {
logger.L().Fatal(err.Error())
}
results.HandleResults()
if err = results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
cautils.SimpleDisplay(os.Stderr, "%s Run with '--verbose' flag for full scan details\n\n", emoji.Detective)
}
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", results.GetRiskScore(), scanInfo.FailThreshold)
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
}
return nil
},
}
}
// func init() {
// scanCmd.AddCommand(frameworkCmd)
// scanInfo = cautils.ScanInfo{}
// }
// 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(scanInfo *cautils.ScanInfo) {
func flagValidationFramework(scanInfo *cautils.ScanInfo) error {
if scanInfo.Submit && scanInfo.Local {
logger.L().Fatal("you can use `keep-local` or `submit`, but not both")
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.FailThreshold {
logger.L().Fatal("bad argument: out of range threshold")
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return fmt.Errorf("bad argument: out of range threshold")
}
return nil
}

View File

@@ -11,7 +11,7 @@ var scanCmdExamples = `
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defind frameworks
# Scan current cluster with all frameworks
kubescape scan --submit --enable-host-scan
kubescape scan --submit --enable-host-scan --verbose
# Scan kubernetes YAML manifest files
kubescape scan *.yaml

View File

@@ -11,6 +11,7 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/cautils/logger"
corev1 "k8s.io/api/core/v1"
)
@@ -68,6 +69,7 @@ type ITenantConfig interface {
// getters
GetClusterName() string
GetAccountID() string
GetTennatEmail() string
GetConfigObj() *ConfigObj
// GetBackendAPI() getter.IBackend
// GenerateURL()
@@ -117,6 +119,7 @@ func NewLocalConfig(
}
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
func (lc *LocalConfig) GetTennatEmail() string { return lc.configObj.CustomerAdminEMail }
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
func (lc *LocalConfig) GetClusterName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
@@ -135,7 +138,10 @@ func (lc *LocalConfig) UpdateCachedConfig() error {
}
func (lc *LocalConfig) DeleteCachedConfig() error {
return DeleteConfigFile()
if err := DeleteConfigFile(); err != nil {
logger.L().Warning(err.Error())
}
return nil
}
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
@@ -228,6 +234,7 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
func (c *ClusterConfig) GetTennatEmail() string { return c.configObj.CustomerAdminEMail }
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
func (c *ClusterConfig) SetTenant() error {
@@ -257,10 +264,10 @@ func (c *ClusterConfig) UpdateCachedConfig() error {
func (c *ClusterConfig) DeleteCachedConfig() error {
if err := c.deleteConfigMap(); err != nil {
return err
logger.L().Warning(err.Error())
}
if err := DeleteConfigFile(); err != nil {
return err
logger.L().Warning(err.Error())
}
return nil
}

View File

@@ -7,7 +7,6 @@ import (
apis "github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
v2 "github.com/armosec/opa-utils/reporthandling/v2"
)
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
@@ -20,6 +19,7 @@ type OPASessionObj struct {
Policies []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
ResourceSource map[string]string // resources sources, map[<rtesource ID>]<resource result>
PostureReport *reporthandling.PostureReport // scan results v1 - Remove
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
@@ -27,9 +27,10 @@ type OPASessionObj struct {
Metadata *reporthandlingv2.Metadata
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
SessionID string // SessionID
}
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
return &OPASessionObj{
Report: &reporthandlingv2.PostureReport{},
Policies: frameworks,
@@ -38,11 +39,13 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
ResourcesResult: make(map[string]resourcesresults.Result),
InfoMap: make(map[string]apis.StatusInfo),
ResourceToControlsMap: make(map[string][]string),
ResourceSource: make(map[string]string),
SessionID: scanInfo.ScanID,
PostureReport: &reporthandling.PostureReport{
ClusterName: ClusterName,
CustomerGUID: CustomerGUID,
},
Metadata: &v2.Metadata{},
Metadata: scanInfoToScanMetadata(scanInfo),
}
}

View File

@@ -2,10 +2,6 @@ package cautils
// CA environment vars
var (
CustomerGUID = ""
ClusterName = ""
EventReceiverURL = ""
NotificationServerURL = ""
DashboardBackendURL = ""
RestAPIPort = "4001"
CustomerGUID = ""
ClusterName = ""
)

View File

@@ -26,7 +26,7 @@ const (
JSON_FILE_FORMAT FileFormat = "json"
)
func LoadResourcesFromFiles(inputPatterns []string) ([]workloadinterface.IMetadata, error) {
func LoadResourcesFromFiles(inputPatterns []string) (map[string][]workloadinterface.IMetadata, error) {
files, errs := listFiles(inputPatterns)
if len(errs) > 0 {
logger.L().Error(fmt.Sprintf("%v", errs))
@@ -42,8 +42,8 @@ func LoadResourcesFromFiles(inputPatterns []string) ([]workloadinterface.IMetada
return workloads, nil
}
func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
workloads := []workloadinterface.IMetadata{}
func loadFiles(filePaths []string) (map[string][]workloadinterface.IMetadata, []error) {
workloads := make(map[string][]workloadinterface.IMetadata, 0)
errs := []error{}
for i := range filePaths {
f, err := loadFile(filePaths[i])
@@ -54,7 +54,12 @@ func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
errs = append(errs, e...)
if w != nil {
workloads = append(workloads, w...)
if _, ok := workloads[filePaths[i]]; !ok {
workloads[filePaths[i]] = []workloadinterface.IMetadata{}
}
wSlice := workloads[filePaths[i]]
wSlice = append(wSlice, w...)
workloads[filePaths[i]] = wSlice
}
}
return workloads, errs

View File

@@ -23,6 +23,20 @@ func TestListFiles(t *testing.T) {
assert.Equal(t, 12, len(files))
}
func TestLoadResourcesFromFiles(t *testing.T) {
workloads, err := LoadResourcesFromFiles([]string{onlineBoutiquePath()})
assert.NoError(t, err)
assert.Equal(t, 12, len(workloads))
for i, w := range workloads {
switch filepath.Base(i) {
case "adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
func TestLoadFiles(t *testing.T) {
files, _ := listFiles([]string{onlineBoutiquePath()})
_, err := loadFiles(files)

View File

@@ -0,0 +1,18 @@
package cautils
import "math"
// Float64ToInt convert float64 to int
func Float64ToInt(x float64) int {
return int(math.Round(x))
}
// Float32ToInt convert float32 to int
func Float32ToInt(x float32) int {
return Float64ToInt(float64(x))
}
// Float16ToInt convert float16 to int
func Float16ToInt(x float32) int {
return Float64ToInt(float64(x))
}

View File

@@ -0,0 +1,24 @@
package cautils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFloat64ToInt(t *testing.T) {
assert.Equal(t, 3, Float64ToInt(3.49))
assert.Equal(t, 4, Float64ToInt(3.5))
assert.Equal(t, 4, Float64ToInt(3.51))
}
func TestFloat32ToInt(t *testing.T) {
assert.Equal(t, 3, Float32ToInt(3.49))
assert.Equal(t, 4, Float32ToInt(3.5))
assert.Equal(t, 4, Float32ToInt(3.51))
}
func TestFloat16ToInt(t *testing.T) {
assert.Equal(t, 3, Float16ToInt(3.49))
assert.Equal(t, 4, Float16ToInt(3.5))
assert.Equal(t, 4, Float16ToInt(3.51))
}

View File

@@ -233,7 +233,14 @@ func (armoAPI *ArmoAPI) GetAccountConfig(clusterName string) (*armotypes.Custome
}
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
return nil, err
// try with default scope
respStr, err = armoAPI.Get(armoAPI.getAccountConfigDefault(clusterName), nil)
if err != nil {
return nil, err
}
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
return nil, err
}
}
return accountConfig, nil

View File

@@ -73,6 +73,12 @@ func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
return u.String()
}
func (armoAPI *ArmoAPI) getAccountConfigDefault(clusterName string) string {
config := armoAPI.getAccountConfig(clusterName)
url := config + "&scope=default"
return url
}
func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
u := url.URL{}
u.Scheme = "https"

View File

@@ -8,10 +8,13 @@ import (
"path/filepath"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/google/uuid"
)
const (
@@ -78,7 +81,7 @@ type ScanInfo struct {
Silent bool // Silent mode - Do not print progress logs
FailThreshold float32 // Failure score threshold
Submit bool // Submit results to Armo BE
ReportID string // Report id of the current scan
ScanID string // Report id of the current scan
HostSensorEnabled BoolPtrFlag // Deploy ARMO K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
@@ -98,6 +101,10 @@ func (scanInfo *ScanInfo) Init() {
scanInfo.setUseFrom()
scanInfo.setOutputFile()
scanInfo.setUseArtifactsFrom()
if scanInfo.ScanID == "" {
scanInfo.ScanID = uuid.NewString()
}
}
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
@@ -187,3 +194,94 @@ func (scanInfo *ScanInfo) contains(policyName string) bool {
}
return false
}
func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata := &reporthandlingv2.Metadata{}
metadata.ScanMetadata.Format = scanInfo.Format
metadata.ScanMetadata.FormatVersion = scanInfo.FormatVersion
metadata.ScanMetadata.Submit = scanInfo.Submit
// TODO - Add excluded and included namespaces
// if len(scanInfo.ExcludedNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.ExcludedNamespaces = strings.Split(scanInfo.ExcludedNamespaces[1:], ",")
// }
// if len(scanInfo.IncludeNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.IncludeNamespaces = strings.Split(scanInfo.IncludeNamespaces[1:], ",")
// }
// scan type
if len(scanInfo.PolicyIdentifier) > 0 {
metadata.ScanMetadata.TargetType = string(scanInfo.PolicyIdentifier[0].Kind)
}
// append frameworks
for _, policy := range scanInfo.PolicyIdentifier {
metadata.ScanMetadata.TargetNames = append(metadata.ScanMetadata.TargetNames, policy.Name)
}
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.FailThreshold = scanInfo.FailThreshold
metadata.ScanMetadata.HostScanner = scanInfo.HostSensorEnabled.GetBool()
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster
if scanInfo.GetScanningEnvironment() == ScanLocalFiles {
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.File
}
inputFiles := ""
if len(scanInfo.InputPatterns) > 0 {
inputFiles = scanInfo.InputPatterns[0]
}
setContextMetadata(&metadata.ContextMetadata, inputFiles)
return metadata
}
func setContextMetadata(contextMetadata *reporthandlingv2.ContextMetadata, input string) {
// if cluster
if input == "" {
contextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{
ContextName: k8sinterface.GetClusterName(),
}
return
}
// if url
if strings.HasPrefix(input, "http") { // TODO - check if can parse
return
}
if !filepath.IsAbs(input) {
if o, err := os.Getwd(); err == nil {
input = filepath.Join(o, input)
}
}
// if single file
if IsFile(input) {
contextMetadata.FileContextMetadata = &reporthandlingv2.FileContextMetadata{
FilePath: input,
HostName: getHostname(),
}
return
}
// if dir/glob
if !IsFile(input) {
contextMetadata.DirectoryContextMetadata = &reporthandlingv2.DirectoryContextMetadata{
BasePath: input,
HostName: getHostname(),
}
return
}
}
func getHostname() string {
if h, e := os.Hostname(); e == nil {
return h
}
return ""
}

View File

@@ -0,0 +1,65 @@
package cautils
import (
"testing"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/stretchr/testify/assert"
)
// func TestSetInputPatterns(t *testing.T) { //Unitest
// {
// scanInfo := ScanInfo{
// InputPatterns: []string{"file"},
// }
// scanInfo.setInputPatterns()
// assert.Equal(t, "file", scanInfo.InputPatterns[0])
// }
// }
func TestSetContextMetadata(t *testing.T) {
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "")
assert.NotNil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)
assert.Nil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata)
}
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "file")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.NotNil(t, ctx.DirectoryContextMetadata)
assert.Nil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata)
}
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "scaninfo_test.go")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)
assert.NotNil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata)
}
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "https://github.com/armosec/kubescape")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)
assert.Nil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata) // TODO
}
}
func TestGetHostname(t *testing.T) {
assert.NotEqual(t, "", getHostname())
}

View File

@@ -130,7 +130,7 @@ func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
g := getPolicyGetter(nil, tenant.GetTennatEmail(), true, nil)
if downloadInfo.Name == "" {
// if framework name not specified - download all frameworks
@@ -172,7 +172,7 @@ func downloadControl(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
g := getPolicyGetter(nil, tenant.GetTennatEmail(), false, nil)
if downloadInfo.Name == "" {
// TODO - support

View File

@@ -48,11 +48,11 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
return nil
}
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan, clusterScan bool) reporter.IReport {
if submit && clusterScan {
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool) reporter.IReport {
if submit {
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID)
}
if tenantConfig.GetAccountID() == "" && fwScan && clusterScan {
if tenantConfig.GetAccountID() == "" {
// Add link only when scanning a cluster using a framework
return reporterv2.NewReportMock(reporterv2.NO_SUBMIT_QUERY, "run kubescape with the '--submit' flag")
}
@@ -60,9 +60,7 @@ func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fw
if !fwScan {
message = "Kubescape does not submit scan results when scanning controls"
}
if !clusterScan {
message = "Kubescape will submit scan results only when scanning a cluster (not YAML files)"
}
return reporterv2.NewReportMock("", message)
}
@@ -153,11 +151,11 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
}
// setPolicyGetter set the policy getter - local file/github release/ArmoAPI
func getPolicyGetter(loadPoliciesFromFile []string, accountID string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
func getPolicyGetter(loadPoliciesFromFile []string, tennatEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
if len(loadPoliciesFromFile) > 0 {
return getter.NewLoadPolicy(loadPoliciesFromFile)
}
if accountID != "" && frameworkScope {
if tennatEmail != "" && frameworkScope {
g := getter.GetArmoAPIConnector() // download policy from ARMO backend
return g
}

View File

@@ -45,7 +45,7 @@ func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
g := getPolicyGetter(nil, tenant.GetTennatEmail(), true, nil)
return listFrameworksNames(g), nil
}
@@ -53,7 +53,7 @@ func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
g := getPolicyGetter(nil, tenant.GetTennatEmail(), false, nil)
l := getter.ListName
if listPolicies.ListIDs {
l = getter.ListID

View File

@@ -86,7 +86,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup reporter & printer objects ======================================
// reporting behavior - setup reporter
reportHandler := getReporter(tenantConfig, scanInfo.ReportID, scanInfo.Submit, scanInfo.FrameworkScan, len(scanInfo.InputPatterns) == 0)
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan)
// setup printer
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode)
@@ -119,7 +119,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// set policy getter only after setting the customerGUID
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetAccountID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTennatEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)

View File

@@ -20,7 +20,7 @@ func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) err
return err
}
// report
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
if err := submitInterfaces.Reporter.Submit(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")

View File

@@ -5,7 +5,7 @@ go 1.17
require (
github.com/armosec/armoapi-go v0.0.58
github.com/armosec/k8s-interface v0.0.68
github.com/armosec/opa-utils v0.0.120
github.com/armosec/opa-utils v0.0.130
github.com/armosec/rbac-utils v0.0.14
github.com/armosec/utils-go v0.0.3
github.com/armosec/utils-k8s-go v0.0.3
@@ -19,6 +19,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/open-policy-agent/opa v0.38.0
github.com/stretchr/testify v1.7.0
github.com/whilp/git-urls v1.0.0
go.uber.org/zap v1.21.0
golang.org/x/mod v0.5.1
gopkg.in/yaml.v2 v2.4.0

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k=
github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.130 h1:uP60M0PzmDtLqvsA/jX8BED9/Ava4n2QG7VCkuI+hwI=
github.com/armosec/opa-utils v0.0.130/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
@@ -771,6 +771,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@@ -16,12 +16,12 @@ var (
MapResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
OsReleaseFile: "hostdata.kubescape.cloud/v1beta0/",
KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0/",
KernelVersion: "hostdata.kubescape.cloud/v1beta0/",
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0/",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0/",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0/",
OsReleaseFile: "hostdata.kubescape.cloud/v1beta0",
KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0",
KernelVersion: "hostdata.kubescape.cloud/v1beta0",
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
}
)

View File

@@ -1,10 +1,9 @@
package opaprocessor
import (
"fmt"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
@@ -90,9 +89,9 @@ func getArmoObjects(k8sResources *cautils.ArmoResources, allResources map[string
groupResources := k8sinterface.ResourceGroupToString(groups, version, resource)
for _, groupResource := range groupResources {
if k8sObj, ok := (*k8sResources)[groupResource]; ok {
if k8sObj == nil {
logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
}
// if k8sObj == nil {
// logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
// }
for i := range k8sObj {
k8sObjects = append(k8sObjects, allResources[k8sObj[i]])
}
@@ -117,7 +116,7 @@ func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[s
for _, groupResource := range groupResources {
if k8sObj, ok := (*k8sResources)[groupResource]; ok {
if k8sObj == nil {
logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
logger.L().Debug("skipping", helpers.String("resource", groupResource))
}
for i := range k8sObj {
k8sObjects = append(k8sObjects, allResources[k8sObj[i]])

View File

@@ -3,7 +3,6 @@ package policyhandler
import (
"fmt"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/pkg/resourcehandler"
"github.com/armosec/opa-utils/reporthandling"
@@ -24,7 +23,8 @@ func NewPolicyHandler(resourceHandler resourcehandler.IResourceHandler) *PolicyH
}
func (policyHandler *PolicyHandler) CollectResources(notification *reporthandling.PolicyNotification, scanInfo *cautils.ScanInfo) (*cautils.OPASessionObj, error) {
opaSessionObj := cautils.NewOPASessionObj(nil, nil)
opaSessionObj := cautils.NewOPASessionObj(nil, nil, scanInfo)
// validate notification
// TODO
policyHandler.getters = &scanInfo.Getters
@@ -49,8 +49,6 @@ func (policyHandler *PolicyHandler) CollectResources(notification *reporthandlin
func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error {
opaSessionObj.Report.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
scanInfoToScanMetadata(opaSessionObj, scanInfo)
resourcesMap, allResources, armoResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj, &notification.Designators)
if err != nil {
return err
@@ -62,31 +60,3 @@ func (policyHandler *PolicyHandler) getResources(notification *reporthandling.Po
return nil
}
func scanInfoToScanMetadata(opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) {
opaSessionObj.Metadata.ClusterMetadata.ContextName = k8sinterface.GetClusterName()
opaSessionObj.Metadata.ScanMetadata.Format = scanInfo.Format
opaSessionObj.Metadata.ScanMetadata.Submit = scanInfo.Submit
// TODO - Add excluded and included namespaces
// if len(scanInfo.ExcludedNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.ExcludedNamespaces = strings.Split(scanInfo.ExcludedNamespaces[1:], ",")
// }
// if len(scanInfo.IncludeNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.IncludeNamespaces = strings.Split(scanInfo.IncludeNamespaces[1:], ",")
// }
// scan type
if len(scanInfo.PolicyIdentifier) > 0 {
opaSessionObj.Metadata.ScanMetadata.TargetType = string(scanInfo.PolicyIdentifier[0].Kind)
}
// append frameworks
for _, policy := range scanInfo.PolicyIdentifier {
opaSessionObj.Metadata.ScanMetadata.TargetNames = append(opaSessionObj.Metadata.ScanMetadata.TargetNames, policy.Name)
}
opaSessionObj.Metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
opaSessionObj.Metadata.ScanMetadata.FailThreshold = scanInfo.FailThreshold
opaSessionObj.Metadata.ScanMetadata.HostScanner = scanInfo.HostSensorEnabled.GetBool()
opaSessionObj.Metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
opaSessionObj.Metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs
}

View File

@@ -32,6 +32,7 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
k8sResources := setK8sResourceMap(sessionObj.Policies)
allResources := map[string]workloadinterface.IMetadata{}
workloadIDToSource := make(map[string]string, 0)
armoResources := &cautils.ArmoResources{}
workloads := []workloadinterface.IMetadata{}
@@ -41,8 +42,11 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
if err != nil {
return nil, allResources, nil, err
}
if w != nil {
workloads = append(workloads, w...)
for source, ws := range w {
workloads = append(workloads, ws...)
for i := range ws {
workloadIDToSource[ws[i].GetID()] = source
}
}
// load resources from url
@@ -50,13 +54,17 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
if err != nil {
return nil, allResources, nil, err
}
if w != nil {
workloads = append(workloads, w...)
for source, ws := range w {
workloads = append(workloads, ws...)
for i := range ws {
workloadIDToSource[ws[i].GetID()] = source
}
}
if len(workloads) == 0 {
return nil, allResources, nil, fmt.Errorf("empty list of workloads - no workloads found")
}
sessionObj.ResourceSource = workloadIDToSource
// map all resources: map["/group/version/kind"][]<k8s workloads>
mappedResources := mapResources(workloads)

View File

@@ -75,7 +75,9 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
if err != nil {
logger.L().Debug("failed to collect worker nodes number", helpers.Error(err))
} else {
sessionObj.Metadata.ClusterMetadata.NumberOfWorkerNodes = numberOfWorkerNodes
if sessionObj.Metadata != nil && sessionObj.Metadata.ContextMetadata.ClusterContextMetadata != nil {
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.NumberOfWorkerNodes = numberOfWorkerNodes
}
}
imgVulnResources := cautils.MapImageVulnResources(armoResourceMap)
@@ -118,7 +120,9 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
logger.L().Warning("failed to collect cloud data", helpers.Error(err))
}
if provider != "" {
sessionObj.Metadata.ClusterMetadata.CloudProvider = provider
if sessionObj.Metadata != nil && sessionObj.Metadata.ContextMetadata.ClusterContextMetadata != nil {
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudProvider = provider
}
}
}
@@ -280,15 +284,8 @@ func getCloudProviderDescription(allResources map[string]workloadinterface.IMeta
wl, err := cloudsupport.GetDescriptiveInfoFromCloudProvider(clusterName, provider, region, project)
if err != nil {
// Return error with useful info on how to configure credentials for getting cloud provider info
switch provider {
case "gke":
return provider, fmt.Errorf("could not get descriptive information about gke cluster: %s using sdk client. See https://developers.google.com/accounts/docs/application-default-credentials for more information", cluster)
case "eks":
return provider, fmt.Errorf("could not get descriptive information about eks cluster: %s using sdk client. Check out how to configure credentials in https://docs.aws.amazon.com/sdk-for-go/api/", cluster)
case "aks":
return provider, fmt.Errorf("could not get descriptive information about aks cluster: %s. %v", cluster, err.Error())
}
return provider, err
logger.L().Debug("failed to get descriptive information", helpers.Error(err))
return provider, fmt.Errorf("failed to get %s descriptive information. Read more: https://hub.armo.cloud/docs/kubescape-integration-with-cloud-providers", strings.ToUpper(provider))
}
allResources[wl.GetID()] = wl
(*armoResourceMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()}

View File

@@ -4,15 +4,27 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/armosec/kubescape/core/cautils/getter"
giturls "github.com/whilp/git-urls"
"k8s.io/utils/strings/slices"
)
type IRepository interface {
parse(fullURL string) error
setBranch(string) error
setTree() error
getYamlFromTree() []string
setIsFile(bool)
getIsFile() bool
getBranch() string
getTree() tree
getFilesFromTree([]string) []string
}
type innerTree struct {
@@ -23,19 +35,24 @@ type tree struct {
}
type GitHubRepository struct {
// name string // <org>/<repo>
host string
name string // <org>/<repo>
owner string //
repo string //
branch string
path string
token string
isFile bool
tree tree
}
type githubDefaultBranchAPI struct {
DefaultBranch string `json:"default_branch"`
}
func NewGitHubRepository(rep string) *GitHubRepository {
func NewGitHubRepository() *GitHubRepository {
return &GitHubRepository{
host: "github",
name: rep,
host: "github.com",
token: os.Getenv("GITHUB_TOKEN"),
}
}
@@ -45,79 +62,143 @@ func ScanRepository(command string, branchOptional string) ([]string, error) {
return nil, err
}
err = repo.setBranch(branchOptional)
if err != nil {
if err := repo.parse(command); err != nil {
return nil, err
}
err = repo.setTree()
if err != nil {
if err := repo.setBranch(branchOptional); err != nil {
return nil, err
}
if err := repo.setTree(); err != nil {
return nil, err
}
// get all paths that are of the yaml type, and build them into a valid url
return repo.getYamlFromTree(), nil
return repo.getFilesFromTree([]string{"yaml", "yml", "json"}), nil
}
func getHostAndRepoName(url string) (string, string, error) {
splitUrl := strings.Split(url, "/")
if len(splitUrl) != 5 {
return "", "", fmt.Errorf("failed to pars url: %s", url)
func getHost(fullURL string) (string, error) {
parsedURL, err := giturls.Parse(fullURL)
if err != nil {
return "", err
}
hostUrl := splitUrl[2] // github.com, gitlab.com, etc.
repository := splitUrl[3] + "/" + strings.Split(splitUrl[4], ".")[0] // user/reposetory
return hostUrl, repository, nil
return parsedURL.Host, nil
}
func getRepository(url string) (IRepository, error) {
hostUrl, repoName, err := getHostAndRepoName(url)
func getRepository(fullURL string) (IRepository, error) {
hostUrl, err := getHost(fullURL)
if err != nil {
return nil, err
}
var repo IRepository
switch repoHost := strings.Split(hostUrl, ".")[0]; repoHost {
case "github":
repo = NewGitHubRepository(repoName)
switch hostUrl {
case "github.com":
repo = NewGitHubRepository()
case "raw.githubusercontent.com":
repo = NewGitHubRepository()
repo.setIsFile(true)
default:
return nil, fmt.Errorf("unknown repository host: %s", repoHost)
return nil, fmt.Errorf("unknown repository host: %s", hostUrl)
}
// Returns the host-url, and the part of the user and repository from the url
return repo, nil
}
func (g *GitHubRepository) parse(fullURL string) error {
parsedURL, err := giturls.Parse(fullURL)
if err != nil {
return err
}
index := 0
splittedRepo := strings.FieldsFunc(parsedURL.Path, func(c rune) bool { return c == '/' })
if len(splittedRepo) < 2 {
return fmt.Errorf("expecting <user>/<repo> in url path, received: '%s'", parsedURL.Path)
}
g.owner = splittedRepo[index]
index += 1
g.repo = splittedRepo[index]
index += 1
// root of repo
if len(splittedRepo) < index+1 {
return nil
}
// is file or dir
switch splittedRepo[index] {
case "blob":
g.isFile = true
index += 1
case "tree":
g.isFile = false
index += 1
}
if len(splittedRepo) < index+1 {
return nil
}
g.branch = splittedRepo[index]
index += 1
if len(splittedRepo) < index+1 {
return nil
}
g.path = strings.Join(splittedRepo[index:], "/")
return nil
}
func (g *GitHubRepository) getBranch() string { return g.branch }
func (g *GitHubRepository) getTree() tree { return g.tree }
func (g *GitHubRepository) setIsFile(isFile bool) { g.isFile = isFile }
func (g *GitHubRepository) getIsFile() bool { return g.isFile }
func (g *GitHubRepository) setBranch(branchOptional string) error {
// Checks whether the repository type is a master or another type.
// By default it is "master", unless the branchOptional came with a value
if branchOptional == "" {
body, err := getter.HttpGetter(&http.Client{}, g.defaultBranchAPI(), nil)
if err != nil {
return err
}
var data githubDefaultBranchAPI
err = json.Unmarshal([]byte(body), &data)
if err != nil {
return err
}
g.branch = data.DefaultBranch
} else {
if branchOptional != "" {
g.branch = branchOptional
}
if g.branch != "" {
return nil
}
body, err := getter.HttpGetter(&http.Client{}, g.defaultBranchAPI(), g.getHeaders())
if err != nil {
return err
}
var data githubDefaultBranchAPI
err = json.Unmarshal([]byte(body), &data)
if err != nil {
return err
}
g.branch = data.DefaultBranch
return nil
}
func (g *GitHubRepository) defaultBranchAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s", g.name)
func joinOwnerNRepo(owner, repo string) string {
return fmt.Sprintf("%s/%s", owner, repo)
}
func (g *GitHubRepository) defaultBranchAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s", joinOwnerNRepo(g.owner, g.repo))
}
func (g *GitHubRepository) getHeaders() map[string]string {
if g.token == "" {
return nil
}
return map[string]string{"Authorization": fmt.Sprintf("token %s", g.token)}
}
func (g *GitHubRepository) setTree() error {
body, err := getter.HttpGetter(&http.Client{}, g.treeAPI(), nil)
if g.isFile {
return nil
}
body, err := getter.HttpGetter(&http.Client{}, g.treeAPI(), g.getHeaders())
if err != nil {
return err
}
@@ -135,14 +216,24 @@ func (g *GitHubRepository) setTree() error {
}
func (g *GitHubRepository) treeAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s/git/trees/%s?recursive=1", g.name, g.branch)
return fmt.Sprintf("https://api.github.com/repos/%s/git/trees/%s?recursive=1", joinOwnerNRepo(g.owner, g.repo), g.branch)
}
// return a list of yaml for a given repository tree
func (g *GitHubRepository) getYamlFromTree() []string {
func (g *GitHubRepository) getFilesFromTree(filesExtensions []string) []string {
var urls []string
if g.isFile {
if slices.Contains(filesExtensions, getFileExtension(g.path)) {
return []string{fmt.Sprintf("%s/%s", g.rowYamlUrl(), g.path)}
} else {
return []string{}
}
}
for _, path := range g.tree.InnerTrees {
if strings.HasSuffix(path.Path, ".yaml") {
if g.path != "" && !strings.HasPrefix(path.Path, g.path) {
continue
}
if slices.Contains(filesExtensions, getFileExtension(path.Path)) {
urls = append(urls, fmt.Sprintf("%s/%s", g.rowYamlUrl(), path.Path))
}
}
@@ -150,5 +241,9 @@ func (g *GitHubRepository) getYamlFromTree() []string {
}
func (g *GitHubRepository) rowYamlUrl() string {
return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s", g.name, g.branch)
return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s", joinOwnerNRepo(g.owner, g.repo), g.branch)
}
func getFileExtension(path string) string {
return strings.TrimPrefix(filepath.Ext(path), ".")
}

View File

@@ -0,0 +1,140 @@
package resourcehandler
import (
"testing"
"github.com/stretchr/testify/assert"
)
var (
urlA = "https://github.com/armosec/kubescape"
urlB = "https://github.com/armosec/kubescape/blob/master/examples/online-boutique/adservice.yaml"
urlC = "https://github.com/armosec/kubescape/tree/master/examples/online-boutique"
urlD = "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml"
)
func TestScanRepository(t *testing.T) {
{
files, err := ScanRepository(urlA, "")
assert.NoError(t, err)
assert.Less(t, 0, len(files))
}
{
files, err := ScanRepository(urlB, "")
assert.NoError(t, err)
assert.Less(t, 0, len(files))
}
{
files, err := ScanRepository(urlC, "")
assert.NoError(t, err)
assert.Less(t, 0, len(files))
}
{
files, err := ScanRepository(urlD, "")
assert.NoError(t, err)
assert.Equal(t, 1, len(files))
}
}
func TestGetHost(t *testing.T) {
{
host, err := getHost(urlA)
assert.NoError(t, err)
assert.Equal(t, "github.com", host)
}
{
host, err := getHost(urlB)
assert.NoError(t, err)
assert.Equal(t, "github.com", host)
}
{
host, err := getHost(urlC)
assert.NoError(t, err)
assert.Equal(t, "github.com", host)
}
{
host, err := getHost(urlD)
assert.NoError(t, err)
assert.Equal(t, "raw.githubusercontent.com", host)
}
}
func TestGithubSetBranch(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.NoError(t, gh.setBranch(""))
assert.Equal(t, "master", gh.getBranch())
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlB))
err := gh.setBranch("dev")
assert.NoError(t, err)
assert.Equal(t, "dev", gh.getBranch())
}
}
func TestGithubSetTree(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.NoError(t, gh.setBranch(""))
err := gh.setTree()
assert.NoError(t, err)
assert.Less(t, 0, len(gh.getTree().InnerTrees))
}
}
func TestGithubGetYamlFromTree(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.NoError(t, gh.setBranch(""))
assert.NoError(t, gh.setTree())
files := gh.getFilesFromTree([]string{"yaml"})
assert.Less(t, 0, len(files))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlB))
assert.NoError(t, gh.setBranch(""))
assert.NoError(t, gh.setTree())
files := gh.getFilesFromTree([]string{"yaml"})
assert.Equal(t, 1, len(files))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlC))
assert.NoError(t, gh.setBranch(""))
assert.NoError(t, gh.setTree())
files := gh.getFilesFromTree([]string{"yaml"})
assert.Equal(t, 12, len(files))
}
}
func TestGithubParse(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.Equal(t, "armosec/kubescape", joinOwnerNRepo(gh.owner, gh.repo))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlB))
assert.Equal(t, "armosec/kubescape", joinOwnerNRepo(gh.owner, gh.repo))
assert.Equal(t, "master", gh.branch)
assert.Equal(t, "examples/online-boutique/adservice.yaml", gh.path)
assert.True(t, gh.isFile)
assert.Equal(t, 1, len(gh.getFilesFromTree([]string{"yaml"})))
assert.Equal(t, 0, len(gh.getFilesFromTree([]string{"yml"})))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlC))
assert.Equal(t, "armosec/kubescape", joinOwnerNRepo(gh.owner, gh.repo))
assert.Equal(t, "master", gh.branch)
assert.Equal(t, "examples/online-boutique", gh.path)
assert.False(t, gh.isFile)
}
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/armosec/kubescape/core/cautils/logger"
)
func loadResourcesFromUrl(inputPatterns []string) ([]workloadinterface.IMetadata, error) {
func loadResourcesFromUrl(inputPatterns []string) (map[string][]workloadinterface.IMetadata, error) {
urls := listUrls(inputPatterns)
if len(urls) == 0 {
return nil, nil
@@ -29,14 +29,10 @@ func listUrls(patterns []string) []string {
urls := []string{}
for i := range patterns {
if strings.HasPrefix(patterns[i], "http") {
if !cautils.IsYaml(patterns[i]) && !cautils.IsJson(patterns[i]) { // if url of repo
if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch
urls = append(urls, yamls...)
} else {
logger.L().Error(err.Error())
}
} else { // url of single file
urls = append(urls, patterns[i])
if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch
urls = append(urls, yamls...)
} else {
logger.L().Error(err.Error())
}
}
}
@@ -44,8 +40,8 @@ func listUrls(patterns []string) []string {
return urls
}
func downloadFiles(urls []string) ([]workloadinterface.IMetadata, []error) {
workloads := []workloadinterface.IMetadata{}
func downloadFiles(urls []string) (map[string][]workloadinterface.IMetadata, []error) {
workloads := make(map[string][]workloadinterface.IMetadata, 0)
errs := []error{}
for i := range urls {
f, err := downloadFile(urls[i])
@@ -56,7 +52,12 @@ func downloadFiles(urls []string) ([]workloadinterface.IMetadata, []error) {
w, e := cautils.ReadFile(f, cautils.GetFileFormat(urls[i]))
errs = append(errs, e...)
if w != nil {
workloads = append(workloads, w...)
if _, ok := workloads[urls[i]]; !ok {
workloads[urls[i]] = make([]workloadinterface.IMetadata, 0)
}
wSlice := workloads[urls[i]]
wSlice = append(wSlice, w...)
workloads[urls[i]] = wSlice
}
}
return workloads, errs

View File

@@ -0,0 +1,66 @@
package resourcehandler
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLoadResourcesFromUrl(t *testing.T) {
{
workloads, err := loadResourcesFromUrl([]string{"https://github.com/armosec/kubescape/tree/master/examples/online-boutique"})
assert.NoError(t, err)
assert.Equal(t, 12, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
{
workloads, err := loadResourcesFromUrl([]string{"https://github.com/armosec/kubescape"})
assert.NoError(t, err)
assert.Less(t, 12, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
{
workloads, err := loadResourcesFromUrl([]string{"https://github.com/armosec/kubescape/blob/master/examples/online-boutique/adservice.yaml"})
assert.NoError(t, err)
assert.Equal(t, 1, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
{
workloads, err := loadResourcesFromUrl([]string{"https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml"})
assert.NoError(t, err)
assert.Equal(t, 1, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
}

View File

@@ -23,7 +23,7 @@ func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", cautils.Float32ToInt(score))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {

View File

@@ -27,7 +27,7 @@ func (prometheusPrinter *PrometheusPrinter) SetWriter(outputFile string) {
}
func (prometheusPrinter *PrometheusPrinter) Score(score float32) {
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", int(score))
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", cautils.Float32ToInt(score))
}
func (printer *PrometheusPrinter) printResources(allResources map[string]workloadinterface.IMetadata, resourcesIDs *reporthandling.ResourcesIDs, frameworkName, controlName string) {

View File

@@ -4,39 +4,125 @@ import (
"fmt"
"sort"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
)
func generateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfoMap map[string]string) []string {
row := []string{controlSummary.GetName()}
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed()))
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded()))
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().All()))
const (
columnSeverity = iota
columnName = iota
columnCounterFailed = iota
columnCounterExclude = iota
columnCounterAll = iota
columnRiskScore = iota
columnInfo = iota
_rowLen = iota
)
if !controlSummary.GetStatus().IsSkipped() {
row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%")
row = append(row, "")
} else {
row = append(row, string(controlSummary.GetStatus().Status()))
if controlSummary.GetStatus().IsSkipped() {
row = append(row, infoToPrintInfoMap[controlSummary.GetStatus().Info()])
} else {
row = append(row, "")
}
func generateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []infoStars, verbose bool) []string {
row := make([]string, _rowLen)
// ignore passed results
if !verbose && (controlSummary.GetStatus().IsPassed()) {
return []string{}
}
// ignore irelevant results
if !verbose && (controlSummary.GetStatus().IsSkipped() && controlSummary.GetStatus().Status() == apis.StatusIrrelevant) {
return []string{}
}
row[columnSeverity] = getSeverityColumn(controlSummary)
row[columnName] = controlSummary.GetName()
row[columnCounterFailed] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())
row[columnCounterExclude] = fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded())
row[columnCounterAll] = fmt.Sprintf("%d", controlSummary.NumberOfResources().All())
row[columnRiskScore] = getRiskScoreColumn(controlSummary)
row[columnInfo] = getInfoColumn(controlSummary, infoToPrintInfo)
return row
}
func getSortedControlsNames(controls reportsummary.ControlSummaries) []string {
controlNames := make([]string, 0, len(controls))
func getInfoColumn(controlSummary reportsummary.IControlSummary, infoToPrintInfo []infoStars) string {
if !controlSummary.GetStatus().IsSkipped() {
return ""
}
if controlSummary.GetStatus().IsSkipped() {
for i := range infoToPrintInfo {
if infoToPrintInfo[i].info == controlSummary.GetStatus().Info() {
return infoToPrintInfo[i].stars
}
}
}
return ""
}
func getRiskScoreColumn(controlSummary reportsummary.IControlSummary) string {
if controlSummary.GetStatus().IsSkipped() {
return string(controlSummary.GetStatus().Status())
}
return fmt.Sprintf("%d", cautils.Float32ToInt(controlSummary.GetScore())) + "%"
}
func getSeverityColumn(controlSummary reportsummary.IControlSummary) string {
// if controlSummary.GetStatus().IsPassed() || controlSummary.GetStatus().IsSkipped() {
// return " "
// }
severity := apis.ControlSeverityToString(controlSummary.GetScoreFactor())
return color.New(getColor(severity), color.Bold).SprintFunc()(severity)
}
func getColor(controlSeverity string) color.Attribute {
switch controlSeverity {
case "Critical":
return color.FgRed
case "High":
return color.FgYellow
case "Medium":
return color.FgCyan
case "Low":
return color.FgWhite
default:
return color.FgWhite
}
}
func getSortedControlsNames(controls reportsummary.ControlSummaries) [][]string {
controlNames := make([][]string, 5)
for k := range controls {
c := controls[k]
controlNames = append(controlNames, c.GetName())
i := apis.ControlSeverityToInt(c.GetScoreFactor())
controlNames[i] = append(controlNames[i], c.GetName())
}
for i := range controlNames {
sort.Strings(controlNames[i])
}
sort.Strings(controlNames)
return controlNames
}
func getControlTableHeaders() []string {
return []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE", "INFO"}
headers := make([]string, _rowLen)
headers[columnName] = "CONTROL NAME"
headers[columnCounterFailed] = "FAILED RESOURCES"
headers[columnCounterExclude] = "EXCLUDED RESOURCES"
headers[columnCounterAll] = "ALL RESOURCES"
headers[columnSeverity] = "SEVERITY"
headers[columnRiskScore] = "% RISK-SCORE"
headers[columnInfo] = "INFO"
return headers
}
func getColumnsAlignments() []int {
alignments := make([]int, _rowLen)
alignments[columnName] = tablewriter.ALIGN_LEFT
alignments[columnCounterFailed] = tablewriter.ALIGN_CENTER
alignments[columnCounterExclude] = tablewriter.ALIGN_CENTER
alignments[columnCounterAll] = tablewriter.ALIGN_CENTER
alignments[columnSeverity] = tablewriter.ALIGN_LEFT
alignments[columnRiskScore] = tablewriter.ALIGN_CENTER
alignments[columnRiskScore] = tablewriter.ALIGN_CENTER
return alignments
}

View File

@@ -24,7 +24,7 @@ func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", cautils.Float32ToInt(score))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {

View File

@@ -100,7 +100,7 @@ func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", cautils.Float32ToInt(score))
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {

View File

@@ -31,8 +31,7 @@ var (
)
type PdfPrinter struct {
writer *os.File
sortedControlNames []string
writer *os.File
}
func NewPdfPrinter() *PdfPrinter {
@@ -52,18 +51,18 @@ func (pdfPrinter *PdfPrinter) SetWriter(outputFile string) {
}
func (pdfPrinter *PdfPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", cautils.Float32ToInt(score))
}
func (pdfPrinter *PdfPrinter) printInfo(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, infoMap map[string]string) {
func (pdfPrinter *PdfPrinter) printInfo(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, infoMap []infoStars) {
emptyRowCounter := 1
for key, val := range infoMap {
if val != "" {
for i := range infoMap {
if infoMap[i].info != "" {
m.Row(5, func() {
m.Col(1, func() {
m.Text(fmt.Sprintf("%v", val))
m.Text(fmt.Sprintf("%v", infoMap[i].info))
})
m.Col(12, func() {
m.Text(fmt.Sprintf("%v", key))
m.Text(fmt.Sprintf("%v", infoMap[i].stars))
})
})
if emptyRowCounter < len(infoMap) {
@@ -76,15 +75,15 @@ func (pdfPrinter *PdfPrinter) printInfo(m pdf.Maroto, summaryDetails *reportsumm
}
func (pdfPrinter *PdfPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
pdfPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls)
sortedControlNames := getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls)
infoToPrintInfoMap := mapInfoToPrintInfo(opaSessionObj.Report.SummaryDetails.Controls)
infoToPrintInfo := mapInfoToPrintInfo(opaSessionObj.Report.SummaryDetails.Controls)
m := pdf.NewMaroto(consts.Portrait, consts.A4)
pdfPrinter.printHeader(m)
pdfPrinter.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks())
pdfPrinter.printTable(m, &opaSessionObj.Report.SummaryDetails)
pdfPrinter.printTable(m, &opaSessionObj.Report.SummaryDetails, sortedControlNames)
pdfPrinter.printFinalResult(m, &opaSessionObj.Report.SummaryDetails)
pdfPrinter.printInfo(m, &opaSessionObj.Report.SummaryDetails, infoToPrintInfoMap)
pdfPrinter.printInfo(m, &opaSessionObj.Report.SummaryDetails, infoToPrintInfo)
// Extrat output buffer.
outBuff, err := m.Output()
@@ -149,15 +148,17 @@ func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsu
}
// Create pdf table
func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) {
func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, sortedControlNames [][]string) {
headers := getControlTableHeaders()
infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls)
controls := make([][]string, len(pdfPrinter.sortedControlNames))
controls := make([][]string, len(sortedControlNames))
for i := range controls {
controls[i] = make([]string, len(headers))
}
for i := 0; i < len(pdfPrinter.sortedControlNames); i++ {
controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, pdfPrinter.sortedControlNames[i]), infoToPrintInfoMap)
for i := len(sortedControlNames) - 1; i >= 0; i-- {
for _, c := range sortedControlNames[i] {
controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, c), infoToPrintInfoMap, true)
}
}
m.TableList(headers, controls, props.TableList{
@@ -186,7 +187,7 @@ func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsum
// Add final results.
func (pdfPrinter *PdfPrinter) printFinalResult(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) {
m.Row(5, func() {
m.Row(_rowLen, func() {
m.Col(3, func() {
m.Text("Resource summary", props.Text{
Align: consts.Left,

View File

@@ -17,10 +17,9 @@ import (
)
type PrettyPrinter struct {
formatVersion string
writer *os.File
verboseMode bool
sortedControlNames []string
formatVersion string
writer *os.File
verboseMode bool
}
func NewPrettyPrinter(verboseMode bool, formatVersion string) *PrettyPrinter {
@@ -31,14 +30,16 @@ func NewPrettyPrinter(verboseMode bool, formatVersion string) *PrettyPrinter {
}
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
prettyPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
sortedControlNames := getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
if prettyPrinter.formatVersion == "v1" {
prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources)
} else if prettyPrinter.formatVersion == "v2" {
prettyPrinter.resourceTable(opaSessionObj.ResourcesResult, opaSessionObj.AllResources)
if prettyPrinter.verboseMode {
if prettyPrinter.formatVersion == "v1" {
prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources, sortedControlNames)
} else if prettyPrinter.formatVersion == "v2" {
prettyPrinter.resourceTable(opaSessionObj.ResourcesResult, opaSessionObj.AllResources)
}
}
prettyPrinter.printSummaryTable(&opaSessionObj.Report.SummaryDetails)
prettyPrinter.printSummaryTable(&opaSessionObj.Report.SummaryDetails, sortedControlNames)
}
@@ -49,13 +50,14 @@ func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}
func (prettyPrinter *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, allResources map[string]workloadinterface.IMetadata) {
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
controlSummary := controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]) // summaryDetails.Controls ListControls().All() Controls.GetControl(ca)
prettyPrinter.printTitle(controlSummary)
prettyPrinter.printResources(controlSummary, allResources)
prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], controlSummary)
func (prettyPrinter *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, allResources map[string]workloadinterface.IMetadata, sortedControlNames [][]string) {
for i := len(sortedControlNames) - 1; i >= 0; i-- {
for _, c := range sortedControlNames[i] {
controlSummary := controls.GetControl(reportsummary.EControlCriteriaName, c) // summaryDetails.Controls ListControls().All() Controls.GetControl(ca)
prettyPrinter.printTitle(controlSummary)
prettyPrinter.printResources(controlSummary, allResources)
prettyPrinter.printSummary(c, controlSummary)
}
}
}
@@ -167,53 +169,61 @@ func generateRelatedObjectsStr(workload WorkloadSummary) string {
}
func generateFooter(summaryDetails *reportsummary.SummaryDetails) []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", summaryDetails.NumberOfResources().Failed()))
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().Excluded()))
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()))
row = append(row, fmt.Sprintf("%.2f%s", summaryDetails.Score, "%"))
row = append(row, " ")
row := make([]string, _rowLen)
row[columnName] = "Resource Summary"
row[columnCounterFailed] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed())
row[columnCounterExclude] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().Excluded())
row[columnCounterAll] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().All())
row[columnSeverity] = " "
row[columnRiskScore] = fmt.Sprintf("%.2f%s", summaryDetails.Score, "%")
row[columnInfo] = " "
return row
}
func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsummary.SummaryDetails) {
func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsummary.SummaryDetails, sortedControlNames [][]string) {
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(getControlTableHeaders())
summaryTable.SetHeaderLine(true)
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
summaryTable.SetColumnAlignment(alignments)
infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls)
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]), infoToPrintInfoMap))
summaryTable.SetColumnAlignment(getColumnsAlignments())
infoToPrintInfo := mapInfoToPrintInfo(summaryDetails.Controls)
for i := len(sortedControlNames) - 1; i >= 0; i-- {
for _, c := range sortedControlNames[i] {
row := generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, c), infoToPrintInfo, prettyPrinter.verboseMode)
if len(row) > 0 {
summaryTable.Append(row)
}
}
}
summaryTable.SetFooter(generateFooter(summaryDetails))
// summaryTable.SetFooter(generateFooter())
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks()))
summaryTable.Render()
prettyPrinter.printInfo(infoToPrintInfoMap)
// For control scan framework will be nil
// When scanning controls the framework list will be empty
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks()))
prettyPrinter.printInfo(infoToPrintInfo)
}
func (prettyPrinter *PrettyPrinter) printInfo(infoToPrintInfoMap map[string]string) {
for info, stars := range infoToPrintInfoMap {
cautils.WarningDisplay(prettyPrinter.writer, fmt.Sprintf("%s - %s\n", stars, info))
func (prettyPrinter *PrettyPrinter) printInfo(infoToPrintInfo []infoStars) {
fmt.Println()
for i := range infoToPrintInfo {
cautils.InfoDisplay(prettyPrinter.writer, fmt.Sprintf("%s %s\n", infoToPrintInfo[i].stars, infoToPrintInfo[i].info))
}
}
func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string {
if len(frameworks) == 1 {
if frameworks[0].GetName() != "" {
return fmt.Sprintf("\nFRAMEWORK %s\n", frameworks[0].GetName())
return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName())
// cautils.InfoTextDisplay(prettyPrinter.writer, ))
}
} else if len(frameworks) > 1 {
p := "\nFRAMEWORKS: "
p := "FRAMEWORKS: "
i := 0
for ; i < len(frameworks)-1; i++ {
p += fmt.Sprintf("%s (risk: %.2f), ", frameworks[i].GetName(), frameworks[i].GetScore())
@@ -224,14 +234,6 @@ func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) stri
return ""
}
// func getSortedControlsNames(controls []reportsummary.IPolicies) []string {
// controlNames := make([]string, 0, len(controls))
// for k := range controls {
// controlNames = append(controlNames, controls[k].Get())
// }
// sort.Strings(controlNames)
// return controlNames
// }
func getControlLink(controlID string) string {
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
}

View File

@@ -29,7 +29,7 @@ func (prometheusPrinter *PrometheusPrinter) SetWriter(outputFile string) {
}
func (prometheusPrinter *PrometheusPrinter) Score(score float32) {
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", int(score))
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", cautils.Float32ToInt(score))
}
func (printer *PrometheusPrinter) generatePrometheusFormat(

View File

@@ -2,8 +2,10 @@ package v2
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
@@ -12,88 +14,183 @@ import (
type metricsName string
const (
metricsFrameworkScore metricsName = "kubescape_risk_score_framework"
metricsControlScore metricsName = "kubescape_risk_score_control"
metricsScore metricsName = "kubescape_risk_score"
metricsresourceFailed metricsName = "kubescape_resource_controls_number_of_failed"
metricsresourcePassed metricsName = "kubescape_resource_controls_number_of_passed"
metricsresourceExcluded metricsName = "kubescape_resource_controls_number_of_exclude"
ksMetrics metricsName = "kubescape"
metricsCluster metricsName = "cluster"
metricsScore metricsName = "riskScore"
metricsCount metricsName = "count"
metricsFailed metricsName = "failed"
metricsExcluded metricsName = "exclude"
metricsPassed metricsName = "passed"
metricsControl metricsName = "control"
metricsControls metricsName = "controls"
metricsResource metricsName = "resource"
metricsResources metricsName = "resources"
metricsFramework metricsName = "framework"
)
func (mrs *mRiskScore) string() string {
r := fmt.Sprintf("resourcesCountFailed=\"%d\"", mrs.resourcesCountFailed) + ","
r += fmt.Sprintf("resourcesCountExcluded=\"%d\"", mrs.resourcesCountExcluded) + ","
r += fmt.Sprintf("resourcesCountPassed=\"%d\"", mrs.resourcesCountPassed) + ","
r += fmt.Sprintf("controlsCountFailed=\"%d\"", mrs.controlsCountFailed) + ","
r += fmt.Sprintf("controlsCountExcluded=\"%d\"", mrs.controlsCountExcluded) + ","
r += fmt.Sprintf("controlsCountPassed=\"%d\"", mrs.controlsCountPassed) + ","
r += fmt.Sprintf("controlsCountSkipped=\"%d\"", mrs.controlsCountSkipped)
return r
// ============================================ CLUSTER ============================================================
func (mrs *mRiskScore) metrics() []string {
/*
##### Overall risk score
kubescape_cluster_riskScore{} <risk score>
###### Overall resources counters
kubescape_cluster_count_resources_failed{} <counter>
kubescape_cluster_count_resources_excluded{} <counter>
kubescape_cluster_count_resources_passed{} <counter>
###### Overall controls counters
kubescape_cluster_count_controls_failed{} <counter>
kubescape_cluster_count_controls_excluded{} <counter>
kubescape_cluster_count_controls_passed{} <counter>
*/
m := []string{}
// overall
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s", mrs.prefix(), metricsScore), mrs.labels(), mrs.riskScore))
// resources
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsResources, metricsFailed), mrs.labels(), mrs.resourcesCountFailed))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsResources, metricsExcluded), mrs.labels(), mrs.resourcesCountExcluded))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsResources, metricsPassed), mrs.labels(), mrs.resourcesCountPassed))
// controls
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsControl, metricsFailed), mrs.labels(), mrs.controlsCountFailed))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsControl, metricsExcluded), mrs.labels(), mrs.controlsCountExcluded))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrs.prefix(), metricsCount, metricsControl, metricsPassed), mrs.labels(), mrs.controlsCountPassed))
return m
}
func (mrs *mRiskScore) value() int {
return mrs.riskScore
func (mrs *mRiskScore) labels() string {
return ""
}
func (mcrs *mControlRiskScore) string() string {
r := fmt.Sprintf("controlName=\"%s\"", mcrs.controlName) + ","
r += fmt.Sprintf("controlID=\"%s\"", mcrs.controlID) + ","
func (mrs *mRiskScore) prefix() string {
return fmt.Sprintf("%s_%s", ksMetrics, metricsCluster)
}
// ============================================ CONTROL ============================================================
func (mcrs *mControlRiskScore) metrics() []string {
/*
# Risk score
kubescape_control_riskScore{name="<control name>",url="<docs url>",severity="<control severity>"} <risk score>
# Resources counters
kubescape_control_count_resources_failed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
kubescape_control_count_resources_excluded{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
kubescape_control_count_resources_passed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
*/
m := []string{}
// overall
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s", mcrs.prefix(), metricsScore), mcrs.labels(), mcrs.riskScore))
// resources
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mcrs.prefix(), metricsCount, metricsResources, metricsFailed), mcrs.labels(), mcrs.resourcesCountFailed))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mcrs.prefix(), metricsCount, metricsResources, metricsExcluded), mcrs.labels(), mcrs.resourcesCountExcluded))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mcrs.prefix(), metricsCount, metricsResources, metricsPassed), mcrs.labels(), mcrs.resourcesCountPassed))
return m
}
func (mcrs *mControlRiskScore) labels() string {
r := fmt.Sprintf("name=\"%s\"", mcrs.controlName) + ","
r += fmt.Sprintf("severity=\"%s\"", mcrs.severity) + ","
r += fmt.Sprintf("resourcesCountFailed=\"%d\"", mcrs.resourcesCountFailed) + ","
r += fmt.Sprintf("resourcesCountExcluded=\"%d\"", mcrs.resourcesCountExcluded) + ","
r += fmt.Sprintf("resourcesCountPassed=\"%d\"", mcrs.resourcesCountPassed) + ","
r += fmt.Sprintf("link=\"%s\"", mcrs.link) + ","
r += fmt.Sprintf("remediation=\"%s\"", mcrs.remediation)
r += fmt.Sprintf("link=\"%s\"", mcrs.link)
return r
}
func (mcrs *mControlRiskScore) value() int {
return mcrs.riskScore
func (mcrs *mControlRiskScore) prefix() string {
return fmt.Sprintf("%s_%s", ksMetrics, metricsControl)
}
func (mfrs *mFrameworkRiskScore) string() string {
r := fmt.Sprintf("frameworkName=\"%s\"", mfrs.frameworkName) + ","
r += fmt.Sprintf("resourcesCountFailed=\"%d\"", mfrs.resourcesCountFailed) + ","
r += fmt.Sprintf("resourcesCountExcluded=\"%d\"", mfrs.resourcesCountExcluded) + ","
r += fmt.Sprintf("resourcesCountPassed=\"%d\"", mfrs.resourcesCountPassed) + ","
r += fmt.Sprintf("controlsCountFailed=\"%d\"", mfrs.controlsCountFailed) + ","
r += fmt.Sprintf("controlsCountExcluded=\"%d\"", mfrs.controlsCountExcluded) + ","
r += fmt.Sprintf("controlsCountPassed=\"%d\"", mfrs.controlsCountPassed) + ","
r += fmt.Sprintf("controlsCountSkipped=\"%d\"", mfrs.controlsCountSkipped)
// ============================================ FRAMEWORK ============================================================
func (mfrs *mFrameworkRiskScore) metrics() []string {
/*
#### Frameworks metrics
kubescape_framework_riskScore{name="<framework name>"} <risk score>
###### Frameworks resources counters
kubescape_framework_count_resources_failed{} <counter>
kubescape_framework_count_resources_excluded{} <counter>
kubescape_framework_count_resources_passed{} <counter>
###### Frameworks controls counters
kubescape_framework_count_controls_failed{name="<framework name>"} <counter>
kubescape_framework_count_controls_excluded{name="<framework name>"} <counter>
kubescape_framework_count_controls_passed{name="<framework name>"} <counter>
*/
m := []string{}
// overall
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s", mfrs.prefix(), metricsScore), mfrs.labels(), mfrs.riskScore))
// resources
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsResources, metricsFailed), mfrs.labels(), mfrs.resourcesCountFailed))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsResources, metricsExcluded), mfrs.labels(), mfrs.resourcesCountExcluded))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsResources, metricsPassed), mfrs.labels(), mfrs.resourcesCountPassed))
// controls
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsControl, metricsFailed), mfrs.labels(), mfrs.controlsCountFailed))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsControl, metricsExcluded), mfrs.labels(), mfrs.controlsCountExcluded))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mfrs.prefix(), metricsCount, metricsControl, metricsPassed), mfrs.labels(), mfrs.controlsCountPassed))
return m
}
func (mfrs *mFrameworkRiskScore) labels() string {
r := fmt.Sprintf("name=\"%s\"", mfrs.frameworkName)
return r
}
func (mfrs *mFrameworkRiskScore) value() int {
return mfrs.riskScore
func (mfrs *mFrameworkRiskScore) prefix() string {
return fmt.Sprintf("%s_%s", ksMetrics, metricsFramework)
}
func (mrc *mResourceControls) string() string {
// ============================================ RESOURCES ============================================================
func (mrc *mResources) metrics() []string {
/*
#### Resources metrics
kubescape_resource_count_controls_failed{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
kubescape_resource_count_controls_excluded{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
*/
m := []string{}
// controls
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrc.prefix(), metricsCount, metricsControls, metricsFailed), mrc.labels(), mrc.controlsCountFailed))
m = append(m, toRowInMetrics(fmt.Sprintf("%s_%s_%s_%s", mrc.prefix(), metricsCount, metricsControls, metricsExcluded), mrc.labels(), mrc.controlsCountExcluded))
return m
}
func (mrc *mResources) labels() string {
r := fmt.Sprintf("apiVersion=\"%s\"", mrc.apiVersion) + ","
r += fmt.Sprintf("kind=\"%s\"", mrc.kind) + ","
r += fmt.Sprintf("namespace=\"%s\"", mrc.namespace) + ","
r += fmt.Sprintf("name=\"%s\"", mrc.name)
return r
}
func (mrc *mResourceControls) value() int {
return mrc.controls
func (mrc *mResources) prefix() string {
return fmt.Sprintf("%s_%s", ksMetrics, metricsResource)
}
func toRowInMetrics(name metricsName, row string, value int) string {
return fmt.Sprintf("%s{%s} %d\n", name, row, value)
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
func toRowInMetrics(name string, row string, value int) string {
return fmt.Sprintf("%s{%s} %d", name, row, value)
}
func (m *Metrics) String() string {
r := toRowInMetrics(metricsScore, m.rs.string(), m.rs.value())
r := strings.Join(m.rs.metrics(), "\n") + "\n"
for i := range m.listFrameworks {
r += toRowInMetrics(metricsFrameworkScore, m.listFrameworks[i].string(), m.listFrameworks[i].value())
r += strings.Join(m.listFrameworks[i].metrics(), "\n") + "\n"
}
for i := range m.listControls {
r += toRowInMetrics(metricsControlScore, m.listControls[i].string(), m.listControls[i].value())
r += strings.Join(m.listControls[i].metrics(), "\n") + "\n"
}
for i := range m.listResourcesControlsFiled {
r += toRowInMetrics(metricsresourceFailed, m.listResourcesControlsFiled[i].string(), m.listResourcesControlsFiled[i].value())
}
for i := range m.listResourcesControlsExcluded {
r += toRowInMetrics(metricsresourceExcluded, m.listResourcesControlsExcluded[i].string(), m.listResourcesControlsExcluded[i].value())
}
for i := range m.listResourcesControlsPassed {
r += toRowInMetrics(metricsresourcePassed, m.listResourcesControlsPassed[i].string(), m.listResourcesControlsPassed[i].value())
for i := range m.listResources {
r += strings.Join(m.listResources[i].metrics(), "\n") + "\n"
}
return r
}
@@ -106,7 +203,7 @@ type mRiskScore struct {
controlsCountFailed int
controlsCountExcluded int
controlsCountSkipped int
riskScore int // metric
riskScore int
}
type mControlRiskScore struct {
@@ -118,7 +215,7 @@ type mControlRiskScore struct {
resourcesCountPassed int
resourcesCountFailed int
resourcesCountExcluded int
riskScore int // metric
riskScore int
}
type mFrameworkRiskScore struct {
@@ -130,23 +227,23 @@ type mFrameworkRiskScore struct {
controlsCountFailed int
controlsCountExcluded int
controlsCountSkipped int
riskScore int // metric
riskScore int
}
type mResourceControls struct {
name string
namespace string
apiVersion string
kind string
controls int // metric
type mResources struct {
name string
namespace string
apiVersion string
kind string
controlsCountPassed int
controlsCountFailed int
controlsCountExcluded int
}
type Metrics struct {
rs mRiskScore
listFrameworks []mFrameworkRiskScore
listControls []mControlRiskScore
listResourcesControlsFiled []mResourceControls
listResourcesControlsPassed []mResourceControls
listResourcesControlsExcluded []mResourceControls
rs mRiskScore
listFrameworks []mFrameworkRiskScore
listControls []mControlRiskScore
listResources []mResources
}
func (mrs *mRiskScore) set(resources reportsummary.ICounters, controls reportsummary.ICounters) {
@@ -176,12 +273,12 @@ func (mcrs *mControlRiskScore) set(resources reportsummary.ICounters) {
}
func (m *Metrics) setRiskScores(summaryDetails *reportsummary.SummaryDetails) {
m.rs.set(summaryDetails.NumberOfResources(), summaryDetails.NumberOfControls())
m.rs.riskScore = int(summaryDetails.GetScore())
m.rs.riskScore = cautils.Float32ToInt(summaryDetails.GetScore())
for _, fw := range summaryDetails.ListFrameworks() {
mfrs := mFrameworkRiskScore{
frameworkName: fw.GetName(),
riskScore: int(fw.GetScore()),
riskScore: cautils.Float32ToInt(fw.GetScore()),
}
mfrs.set(fw.NumberOfResources(), fw.NumberOfControls())
m.listFrameworks = append(m.listFrameworks, mfrs)
@@ -191,7 +288,7 @@ func (m *Metrics) setRiskScores(summaryDetails *reportsummary.SummaryDetails) {
mcrs := mControlRiskScore{
controlName: control.GetName(),
controlID: control.GetID(),
riskScore: int(control.GetScore()),
riskScore: cautils.Float32ToInt(control.GetScore()),
link: getControlLink(control.GetID()),
severity: apis.ControlSeverityToString(control.GetScoreFactor()),
remediation: control.GetRemediation(),
@@ -229,24 +326,18 @@ func (m *Metrics) setResourcesCounters(
}
passed, excluded, failed := resourceControlStatusCounters(&result)
mrc := mResourceControls{}
mrc := mResources{}
mrc.apiVersion = r.GetApiVersion()
mrc.namespace = r.GetNamespace()
mrc.kind = r.GetKind()
mrc.name = r.GetName()
// append
if passed > 0 {
mrc.controls = passed
m.listResourcesControlsPassed = append(m.listResourcesControlsPassed, mrc)
}
if failed > 0 {
mrc.controls = failed
m.listResourcesControlsFiled = append(m.listResourcesControlsFiled, mrc)
}
if excluded > 0 {
mrc.controls = excluded
m.listResourcesControlsExcluded = append(m.listResourcesControlsExcluded, mrc)
}
mrc.controlsCountPassed = passed
mrc.controlsCountFailed = failed
mrc.controlsCountExcluded = excluded
m.listResources = append(m.listResources, mrc)
}
}

View File

@@ -1 +0,0 @@
package v2

View File

@@ -26,8 +26,7 @@ func DataToJson(data *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
report.Results = make([]resourcesresults.Result, len(data.ResourcesResult))
finalizeResults(report.Results, data.ResourcesResult)
report.Resources = make([]reporthandling.Resource, 0) // do not initialize slice length
finalizeResources(report.Resources, report.Results, data.AllResources)
report.Resources = finalizeResources(report.Results, data.AllResources)
return &report
}
@@ -39,21 +38,32 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri
}
}
func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) map[string]string {
infoToPrintInfoMap := make(map[string]string)
type infoStars struct {
stars string
info string
}
func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) []infoStars {
infoToPrintInfo := []infoStars{}
infoToPrintInfoMap := map[string]interface{}{}
starCount := "*"
for _, control := range controls {
if control.GetStatus().IsSkipped() && control.GetStatus().Info() != "" {
if _, ok := infoToPrintInfoMap[control.GetStatus().Info()]; !ok {
infoToPrintInfoMap[control.GetStatus().Info()] = starCount
infoToPrintInfo = append(infoToPrintInfo, infoStars{
info: control.GetStatus().Info(),
stars: starCount,
})
starCount += starCount
infoToPrintInfoMap[control.GetStatus().Info()] = nil
}
}
}
return infoToPrintInfoMap
return infoToPrintInfo
}
func finalizeResources(resources []reporthandling.Resource, results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) {
func finalizeResources(results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) []reporthandling.Resource {
resources := make([]reporthandling.Resource, 0)
for i := range results {
if obj, ok := allResources[results[i].ResourceID]; ok {
r := *reporthandling.NewResource(obj.GetObject())
@@ -61,6 +71,7 @@ func finalizeResources(resources []reporthandling.Resource, results []resourcesr
resources = append(resources, r)
}
}
return resources
}
func logOUtputFile(fileName string) {

View File

@@ -3,8 +3,9 @@ package reporter
import "github.com/armosec/kubescape/core/cautils"
type IReport interface {
ActionSendReport(opaSessionObj *cautils.OPASessionObj) error
Submit(opaSessionObj *cautils.OPASessionObj) error
SetCustomerGUID(customerGUID string)
SetClusterName(clusterName string)
DisplayReportURL()
GetURL() string
}

View File

@@ -38,7 +38,7 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive
}
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj) error {
if opaSessionObj.PostureReport == nil && opaSessionObj.Report != nil {
cautils.ReportV2ToV1(opaSessionObj)
}
@@ -157,6 +157,9 @@ func (report *ReportEventReceiver) generateMessage() {
report.message = fmt.Sprintf("%s %s", message, u.String())
}
func (report *ReportEventReceiver) GetURL() string {
return getter.GetArmoAPIConnector().GetFrontendURL()
}
func (report *ReportEventReceiver) DisplayReportURL() {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
}

View File

@@ -21,7 +21,7 @@ func NewReportMock(query, message string) *ReportMock {
message: message,
}
}
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
func (reportMock *ReportMock) Submit(opaSessionObj *cautils.OPASessionObj) error {
return nil
}
@@ -31,6 +31,10 @@ func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
func (reportMock *ReportMock) SetClusterName(clusterName string) {
}
func (reportMock *ReportMock) GetURL() string {
return getter.GetArmoAPIConnector().GetFrontendURL()
}
func (reportMock *ReportMock) DisplayReportURL() {
u := fmt.Sprintf("https://%s/account/login", getter.GetArmoAPIConnector().GetFrontendURL())
if reportMock.query != "" {

View File

@@ -7,11 +7,11 @@ import (
"net/url"
"os"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/google/uuid"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
@@ -42,34 +42,28 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string) *R
}
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
finalizeReport(opaSessionObj)
func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj) error {
if report.customerGUID == "" {
logger.L().Warning("failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Contact ARMO team for more details")
return nil
}
if report.clusterName == "" {
if opaSessionObj.Metadata.ScanMetadata.ScanningTarget == reporthandlingv2.Cluster && report.clusterName == "" {
logger.L().Warning("failed to publish results because the cluster name is Unknown. If you are scanning YAML files the results are not submitted to the Kubescape SaaS")
return nil
}
if opaSessionObj.Report.ReportID == "" {
opaSessionObj.Report.ReportID = uuid.NewString()
}
opaSessionObj.Report.ReportID = uuid.NewString()
opaSessionObj.Report.CustomerGUID = report.customerGUID
opaSessionObj.Report.ClusterName = report.clusterName
opaSessionObj.Report.Metadata = *opaSessionObj.Metadata
if err := report.prepareReport(opaSessionObj.Report); err != nil {
logger.L().Error("failed to publish results", helpers.Error(err))
} else {
err := report.prepareReport(opaSessionObj)
if err == nil {
report.generateMessage()
} else {
err = fmt.Errorf("failed to submit scan results. url: '%s'", report.GetURL())
}
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
return nil
return err
}
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
@@ -80,80 +74,111 @@ func (report *ReportEventReceiver) SetClusterName(clusterName string) {
report.clusterName = cautils.AdoptClusterName(clusterName) // clean cluster name
}
func (report *ReportEventReceiver) prepareReport(postureReport *reporthandlingv2.PostureReport) error {
func (report *ReportEventReceiver) prepareReport(opaSessionObj *cautils.OPASessionObj) error {
report.initEventReceiverURL()
host := hostToString(report.eventReceiverURL, postureReport.ReportID)
host := hostToString(report.eventReceiverURL, report.reportID)
cautils.StartSpinner()
reportCounter := 0
// send resources
err := report.sendResources(host, postureReport, &reportCounter, false)
err := report.sendResources(host, opaSessionObj)
cautils.StopSpinner()
return err
}
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
splittedPostureReport := setSubReport(postureReport)
func (report *ReportEventReceiver) GetURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
} else {
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
}
return u.String()
}
func (report *ReportEventReceiver) sendResources(host string, opaSessionObj *cautils.OPASessionObj) error {
splittedPostureReport := report.setSubReport(opaSessionObj)
counter := 0
for _, v := range postureReport.Resources {
r, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.ResourceID, err)
}
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete resources
splittedPostureReport.Resources = []reporthandling.Resource{}
splittedPostureReport.Results = []resourcesresults.Result{}
// restart counter
counter = 0
}
counter += len(r)
splittedPostureReport.Resources = append(splittedPostureReport.Resources, v)
reportCounter := 0
if err := report.setResources(splittedPostureReport, opaSessionObj.AllResources, opaSessionObj.ResourceSource, &counter, &reportCounter, host); err != nil {
return err
}
if err := report.setResults(splittedPostureReport, opaSessionObj.ResourcesResult, &counter, &reportCounter, host); err != nil {
return err
}
for _, v := range postureReport.Results {
return report.sendReport(host, splittedPostureReport, reportCounter, true)
}
func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.PostureReport, results map[string]resourcesresults.Result, counter, reportCounter *int, host string) error {
for _, v := range results {
r, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
}
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Results) > 0 {
if *counter+len(r) >= MAX_REPORT_SIZE && len(reportObj.Results) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
if err := report.sendReport(host, reportObj, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete results
splittedPostureReport.Results = []resourcesresults.Result{}
splittedPostureReport.Resources = []reporthandling.Resource{}
reportObj.Results = []resourcesresults.Result{}
reportObj.Resources = []reporthandling.Resource{}
// restart counter
counter = 0
*counter = 0
}
counter += len(r)
splittedPostureReport.Results = append(splittedPostureReport.Results, v)
*counter += len(r)
reportObj.Results = append(reportObj.Results, v)
}
return report.sendReport(host, splittedPostureReport, *reportCounter, true)
return nil
}
func (report *ReportEventReceiver) setResources(reportObj *reporthandlingv2.PostureReport, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]string, counter, reportCounter *int, host string) error {
for resourceID, v := range allResources {
resource := reporthandling.NewResourceIMetadata(v)
if r, ok := resourcesSource[resourceID]; ok {
resource.SetSource(&reporthandling.Source{Path: r})
}
r, err := json.Marshal(resource)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", resourceID, err)
}
if *counter+len(r) >= MAX_REPORT_SIZE && len(reportObj.Resources) > 0 {
// send report
if err := report.sendReport(host, reportObj, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete resources
reportObj.Resources = []reporthandling.Resource{}
reportObj.Results = []resourcesresults.Result{}
// restart counter
*counter = 0
}
*counter += len(r)
reportObj.Resources = append(reportObj.Resources, *resource)
}
return nil
}
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
postureReport.PaginationInfo = reporthandlingv2.PaginationMarks{
ReportNumber: counter,
@@ -171,26 +196,12 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
}
func (report *ReportEventReceiver) generateMessage() {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
} else {
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
}
report.message = ""
sep := "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
report.message = sep
report.message += " << WOW! Now you can see the scan results on the web >>\n\n"
report.message += fmt.Sprintf(" %s\n", u.String())
report.message += fmt.Sprintf(" %s\n", report.GetURL())
report.message += sep
}

View File

@@ -3,9 +3,8 @@ package v2
import (
"net/url"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/google/uuid"
)
@@ -33,23 +32,22 @@ func hostToString(host *url.URL, reportID string) string {
return host.String()
}
func setSubReport(postureReport *reporthandlingv2.PostureReport) *reporthandlingv2.PostureReport {
return &reporthandlingv2.PostureReport{
CustomerGUID: postureReport.CustomerGUID,
ClusterName: postureReport.ClusterName,
ReportID: postureReport.ReportID,
ReportGenerationTime: postureReport.ReportGenerationTime,
SummaryDetails: postureReport.SummaryDetails,
Attributes: postureReport.Attributes,
ClusterCloudProvider: postureReport.ClusterCloudProvider,
JobID: postureReport.JobID,
ClusterAPIServerInfo: postureReport.ClusterAPIServerInfo,
Metadata: postureReport.Metadata,
func (report *ReportEventReceiver) setSubReport(opaSessionObj *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
reportObj := &reporthandlingv2.PostureReport{
CustomerGUID: report.customerGUID,
ClusterName: report.clusterName,
ReportID: report.reportID,
ReportGenerationTime: opaSessionObj.Report.ReportGenerationTime,
SummaryDetails: opaSessionObj.Report.SummaryDetails,
Attributes: opaSessionObj.Report.Attributes,
ClusterAPIServerInfo: opaSessionObj.Report.ClusterAPIServerInfo,
}
}
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {
return &reporthandling.Resource{
ResourceID: obj.GetID(),
Object: obj.GetObject(),
if opaSessionObj.Metadata != nil {
reportObj.Metadata = *opaSessionObj.Metadata
if opaSessionObj.Metadata.ContextMetadata.ClusterContextMetadata != nil {
reportObj.ClusterCloudProvider = opaSessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudProvider // DEPRECATED
reportObj.Metadata.ClusterMetadata = *opaSessionObj.Metadata.ContextMetadata.ClusterContextMetadata
}
}
return reportObj
}

View File

@@ -2,47 +2,8 @@ package v2
import (
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
)
// finalizeV2Report finalize the results objects by copying data from map to lists
func finalizeReport(opaSessionObj *cautils.OPASessionObj) {
opaSessionObj.PostureReport = nil
if len(opaSessionObj.Report.Results) == 0 {
opaSessionObj.Report.Results = make([]resourcesresults.Result, len(opaSessionObj.ResourcesResult))
finalizeResults(opaSessionObj.Report.Results, opaSessionObj.ResourcesResult)
opaSessionObj.ResourcesResult = nil
}
if len(opaSessionObj.Report.Resources) == 0 {
opaSessionObj.Report.Resources = make([]reporthandling.Resource, 0) // do not set slice length
finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.AllResources)
opaSessionObj.AllResources = nil
}
}
func finalizeResults(results []resourcesresults.Result, resourcesResult map[string]resourcesresults.Result) {
index := 0
for resourceID := range resourcesResult {
results[index] = resourcesResult[resourceID]
index++
}
}
func finalizeResources(resources []reporthandling.Resource, allResources map[string]workloadinterface.IMetadata) {
for resourceID := range allResources {
if obj, ok := allResources[resourceID]; ok {
r := *reporthandling.NewResource(obj.GetObject())
r.ResourceID = resourceID
resources = append(resources, r)
}
}
}
func maskID(id string) string {
sep := "-"
splitted := strings.Split(id, sep)

View File

@@ -56,17 +56,19 @@ func (resultsHandler *ResultsHandler) ToJson() ([]byte, error) {
}
// HandleResults handle the scan results according to the pre defind interfaces
func (resultsHandler *ResultsHandler) HandleResults() {
func (resultsHandler *ResultsHandler) HandleResults() error {
resultsHandler.printerObj.ActionPrint(resultsHandler.scanData)
if err := resultsHandler.reporterObj.ActionSendReport(resultsHandler.scanData); err != nil {
logger.L().Error(err.Error())
if err := resultsHandler.reporterObj.Submit(resultsHandler.scanData); err != nil {
return err
}
resultsHandler.printerObj.Score(resultsHandler.GetRiskScore())
resultsHandler.reporterObj.DisplayReportURL()
return nil
}
// NewPrinter defind output format

BIN
docs/ksfromcodetodeploy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

View File

@@ -5,33 +5,33 @@
Kubescape roadmap items are labeled based on where the feature is used and by their maturity.
The features serve different stages of the workflow of the users:
* **Development phase** (writing Kubernetes manifests) - example: VS Code extension is used while editing YAMLs
* **Development phase** (writing Kubernetes manifests) - example: The VS Code extension is used while editing YAMLs.
* **CI phase** (integrating manifests to GIT repo) - example: GitHub action validating HELM charts on PRs
* **CD phase** (deploying applications in Kubernetes) - example: running cluster scan after a new deployment
* **Monitoring phase** (scanning application in Kubernetes) - example: Prometheus scraping the cluster security risk
* **CD phase** (deploying applications in Kubernetes) - example: running a cluster scan after a new deployment
* **Monitoring phase** (scanning application in Kubernetes) - example: Prometheus scraping the cluster security risk
The items in Kubescape roadmap are split to 3 major groups based on the feature planning maturity:
The items in the Kubescape roadmap are split into 3 major groups based on the feature planning maturity:
* [Planning](#planning) - we have tickets open for these issues with more or less clear vision of design
* [Backlog](#backlog) - feature which were discussed at a high level but are not ready for development
* [Wishlist](#wishlist) - features we are dreaming of 😀 and want to push them gradually forward
* [Planning](#planning) - we have tickets open for these issues with a more or less clear vision of design.
* [Backlog](#backlog) - features that were discussed at a high level but are not ready for development
* [Wishlist](#wishlist) - features we are dreaming of in 😀 and want to push them gradually forward
## Planning 👷
* ##### Integration with image registries
We want to expand Kubescape to integrate with differnet image registries and read image vulnerability information from there. This will allow Kubescape to give contextual security information about vulnerabilities [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
We want to expand Kubescape to integrate with different image registries and read image vulnerability information from there. This will allow Kubescape to give contextual security information about vulnerabilities. Container registry integration
* ##### Kubescape as a microservice
Create a REST API for Kubescape so it can run constantly in a cluster and other components like Prometheus can scrape results
Create a REST API for Kubescape so it can constantly run in a cluster, and other components like Prometheus can scrape results.
* ##### Kubescape CLI control over cluster operations
Add functionality to Kubescape CLI to trigger operations in Kubescape cluster components (example: trigger images scans and etc.)
Add functionality to Kubescape CLI to trigger operations in Kubescape cluster components (example: trigger image scans, etc.)
* ##### Produce md/HTML reports
Create scan reports for different output formats
Create scan reports for different output formats.
* ##### Git integration for pull requests
Create insightful GitHub actions for Kubescape
## Backlog 📅
* ##### JSON path for HELM charts
Today Kubescape can point to issues in the Kubernetes object, we want to develop this feature so Kubescape will be able to point to the misconfigured source file (HELM)
Today, Kubescape can point to issues in the Kubernetes object. We want to develop this feature so Kubescape will be able to point to the misconfigured source file (HELM).
* ##### Create Kubescape HELM plugin
* ##### Kubescape based admission controller
Implement admission controller API for Kubescape microservice to enable users to use Kubescape rules as policies

View File

@@ -17,4 +17,106 @@
```bash
kubectl apply -f podmonitor.yaml
```
## Metrics
All kubescape related metrics begin with `kubescape`
> `riskScore` is the output of an algorithm calculating the risk of the vulinrability. `0` indicates there is no risk and `100` indicates highest risk.
#### Cluster scope metrics
##### Overall risk score
```
# Overall riskScore of the scan
kubescape_cluster_riskScore{} <risk score>
```
###### Overall resources counters
```
# Number of resources that failed
kubescape_cluster_count_resources_failed{} <counter>
# Number of resources that where excluded
kubescape_cluster_count_resources_excluded{} <counter>
# Number of resources that passed
kubescape_cluster_count_resources_passed{} <counter>
```
###### Overall controls counters
```
# Number of controls that failed
kubescape_cluster_count_controls_failed{} <counter>
# Number of controls that where excluded
kubescape_cluster_count_controls_excluded{} <counter>
# Number of controls that passed
kubescape_cluster_count_controls_passed{} <counter>
```
#### Frameworks metrics
##### Frameworks risk score
```
kubescape_framework_riskScore{name="<framework name>"} <risk score>
```
###### Frameworks resources counters
```
# Number of resources that failed
kubescape_framework_count_resources_failed{} <counter>
# Number of resources that where excluded
kubescape_framework_count_resources_excluded{} <counter>
# Number of resources that passed
kubescape_framework_count_resources_passed{} <counter>
```
###### Frameworks controls counters
```
# Number of controls that failed
kubescape_framework_count_controls_failed{name="<framework name>"} <counter>
# Number of controls that where excluded
kubescape_framework_count_controls_excluded{name="<framework name>"} <counter>
# Number of controls that passed
kubescape_framework_count_controls_passed{name="<framework name>"} <counter>
```
#### Controls metrics
##### Controls risk score
```
kubescape_control_riskScore{name="<control name>",url="<docs url>",severity="<control severity>"} <risk score>
```
###### Controls resources counters
```
# Number of resources that failed
kubescape_control_count_resources_failed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
# Number of resources that where excluded
kubescape_control_count_resources_excluded{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
# Number of resources that passed
kubescape_control_count_resources_passed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
```
#### Resources metrics
The resources metrics give you the ability to prioritize fixing the resources by the number of controls that failed
```
# Number of controls that failed for this particular resource
kubescape_resource_count_controls_failed{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
# Number of controls that where excluded for this particular resource
kubescape_resource_count_controls_excluded{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
```

View File

@@ -6,6 +6,7 @@ replace github.com/armosec/kubescape/core => ../core
require (
github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000
github.com/armosec/opa-utils v0.0.130
github.com/armosec/utils-go v0.0.3
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
@@ -24,7 +25,6 @@ require (
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armosec/armoapi-go v0.0.58 // indirect
github.com/armosec/k8s-interface v0.0.68 // indirect
github.com/armosec/opa-utils v0.0.120 // indirect
github.com/armosec/rbac-utils v0.0.14 // indirect
github.com/armosec/utils-k8s-go v0.0.3 // indirect
github.com/aws/aws-sdk-go v1.41.11 // indirect
@@ -89,6 +89,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/whilp/git-urls v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k=
github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.130 h1:uP60M0PzmDtLqvsA/jX8BED9/Ava4n2QG7VCkuI+hwI=
github.com/armosec/opa-utils v0.0.130/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
@@ -772,6 +772,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@@ -57,7 +57,7 @@ func getPrometheusDefaultScanCommand(scanID string) *cautils.ScanInfo {
scanInfo := defaultScanInfo()
scanInfo.FrameworkScan = true
scanInfo.ScanAll = true // scan all frameworks
scanInfo.ReportID = scanID // scan ID
scanInfo.ScanID = scanID // scan ID
scanInfo.FailThreshold = 100 // Do not fail scanning
scanInfo.Output = scanID // results output
scanInfo.Format = envToString("KS_FORMAT", "prometheus") // default output should be json

View File

@@ -11,7 +11,7 @@ func TestGetPrometheusDefaultScanCommand(t *testing.T) {
scanID := "1234"
scanInfo := getPrometheusDefaultScanCommand(scanID)
assert.Equal(t, scanID, scanInfo.ReportID)
assert.Equal(t, scanID, scanInfo.ScanID)
assert.Equal(t, scanID, scanInfo.Output)
assert.Equal(t, "prometheus", scanInfo.Format)
// assert.False(t, *scanInfo.HostSensorEnabled.Get())

View File

@@ -77,7 +77,7 @@ func findFile(targetDir string, fileName string) (string, error) {
func getScanCommand(scanRequest *PostScanRequest, scanID string) *cautils.ScanInfo {
scanInfo := scanRequest.ToScanInfo()
scanInfo.ReportID = scanID
scanInfo.ScanID = scanID
// *** start ***
// Set default format

View File

@@ -54,6 +54,6 @@ echo -e "\033[0m"
$KUBESCAPE_EXEC version
echo
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan --submit --enable-host-scan"
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan --submit --enable-host-scan --verbose"
echo -e "\033[0m"