Compare commits

...

30 Commits

Author SHA1 Message Date
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
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
Rotem Refael
9c5759286f Merge pull request #465 from dwertent/master
Update url support
2022-03-27 16:29:01 +03:00
David Wertenteil
efbb123fce Merge pull request #464 from dwertent/master
update error display
2022-03-23 09:27:58 +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
31 changed files with 646 additions and 257 deletions

View File

@@ -115,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

View File

@@ -7,7 +7,7 @@ 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.127
github.com/armosec/opa-utils v0.0.129
github.com/armosec/rbac-utils v0.0.14
github.com/google/uuid v1.3.0
github.com/mattn/go-isatty v0.0.14

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.127 h1:uVyH/+pjpiA0oBO6QODjtdCiPgHx7KQ/TcxxkPvAl4c=
github.com/armosec/opa-utils v0.0.127/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.129 h1:5ezMd558f9MtLlwLWHBeyrBGThaFYSeGhY9f1KQPemw=
github.com/armosec/opa-utils v0.0.129/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=

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

@@ -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

@@ -69,6 +69,7 @@ type ITenantConfig interface {
// getters
GetClusterName() string
GetAccountID() string
GetTennatEmail() string
GetConfigObj() *ConfigObj
// GetBackendAPI() getter.IBackend
// GenerateURL()
@@ -118,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() }
@@ -232,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 {

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

@@ -104,6 +104,7 @@ func (scanInfo *ScanInfo) Init() {
if scanInfo.ScanID == "" {
scanInfo.ScanID = uuid.NewString()
}
}
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
@@ -197,7 +198,6 @@ func (scanInfo *ScanInfo) contains(policyName string) bool {
func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata := &reporthandlingv2.Metadata{}
metadata.ClusterMetadata.ContextName = k8sinterface.GetClusterName()
metadata.ScanMetadata.Format = scanInfo.Format
metadata.ScanMetadata.Submit = scanInfo.Submit
@@ -226,8 +226,61 @@ func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster
if scanInfo.GetScanningEnvironment() == ScanLocalFiles {
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Files
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

@@ -151,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

@@ -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

@@ -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.127
github.com/armosec/opa-utils v0.0.129
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

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.127 h1:uVyH/+pjpiA0oBO6QODjtdCiPgHx7KQ/TcxxkPvAl4c=
github.com/armosec/opa-utils v0.0.127/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.129 h1:5ezMd558f9MtLlwLWHBeyrBGThaFYSeGhY9f1KQPemw=
github.com/armosec/opa-utils v0.0.129/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=

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,46 +4,124 @@ import (
"fmt"
"sort"
"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, infoToPrintInfo []infoStars) []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() {
stars := ""
for i := range infoToPrintInfo {
if infoToPrintInfo[i].info == controlSummary.GetStatus().Info() {
stars = infoToPrintInfo[i].stars
break
}
}
row = append(row, stars)
} 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", int(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

@@ -31,8 +31,7 @@ var (
)
type PdfPrinter struct {
writer *os.File
sortedControlNames []string
writer *os.File
}
func NewPdfPrinter() *PdfPrinter {
@@ -76,13 +75,13 @@ 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)
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, infoToPrintInfo)
@@ -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,27 +169,33 @@ 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)
summaryTable.SetColumnAlignment(getColumnsAlignments())
infoToPrintInfo := mapInfoToPrintInfo(summaryDetails.Controls)
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]), infoToPrintInfo))
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))
@@ -226,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

@@ -2,6 +2,7 @@ package v2
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling/apis"
@@ -12,88 +13,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 +202,7 @@ type mRiskScore struct {
controlsCountFailed int
controlsCountExcluded int
controlsCountSkipped int
riskScore int // metric
riskScore int
}
type mControlRiskScore struct {
@@ -118,7 +214,7 @@ type mControlRiskScore struct {
resourcesCountPassed int
resourcesCountFailed int
resourcesCountExcluded int
riskScore int // metric
riskScore int
}
type mFrameworkRiskScore struct {
@@ -130,23 +226,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) {
@@ -229,24 +325,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

@@ -57,7 +57,7 @@ func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj)
if err == nil {
report.generateMessage()
} else {
logger.L().Debug(err.Error()) // print original error only in debug mode
err = fmt.Errorf("failed to submit scan results. url: '%s'", report.GetURL())
}

View File

@@ -43,8 +43,11 @@ func (report *ReportEventReceiver) setSubReport(opaSessionObj *cautils.OPASessio
ClusterAPIServerInfo: opaSessionObj.Report.ClusterAPIServerInfo,
}
if opaSessionObj.Metadata != nil {
reportObj.ClusterCloudProvider = opaSessionObj.Metadata.ClusterMetadata.CloudProvider
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

@@ -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.Submit(resultsHandler.scanData); err != nil {
logger.L().Error(err.Error())
return err
}
resultsHandler.printerObj.Score(resultsHandler.GetRiskScore())
resultsHandler.reporterObj.DisplayReportURL()
return nil
}
// NewPrinter defind output format

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,7 +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.127
github.com/armosec/opa-utils v0.0.129
github.com/armosec/utils-go v0.0.3
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.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.127 h1:uVyH/+pjpiA0oBO6QODjtdCiPgHx7KQ/TcxxkPvAl4c=
github.com/armosec/opa-utils v0.0.127/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.129 h1:5ezMd558f9MtLlwLWHBeyrBGThaFYSeGhY9f1KQPemw=
github.com/armosec/opa-utils v0.0.129/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=

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"