Compare commits

..

19 Commits

Author SHA1 Message Date
dwertent
bd24f35738 update tag 2021-10-26 12:26:44 +03:00
Rotem Refael
5cf3244918 Merge pull request #192 from dwertent/master
Update multiple score
2021-10-25 17:40:19 +03:00
dwertent
934c9ccc8b fixed lowest 2021-10-25 15:51:23 +03:00
dwertent
41dfdfd1e8 support more than score 2021-10-25 15:14:31 +03:00
David Wertenteil
427fb59c99 Merge pull request #190 from dwertent/master
Fixed submit and url
2021-10-25 12:08:23 +03:00
David Wertenteil
ae825800f6 Merge pull request #189 from Daniel-GrunbergerCA/master
Update tag for newest release of k8s-interface
2021-10-25 12:08:06 +03:00
dwertent
d72700acf6 update submit 2021-10-25 12:05:51 +03:00
dwertent
3310a6a26f Merge remote-tracking branch 'upstream/dev' 2021-10-25 11:55:30 +03:00
dwertent
740b5aa772 add full url 2021-10-25 11:55:08 +03:00
Daniel-GrunbergerCA
04b55e764a fix k8s-interface pkg tag 2021-10-25 10:44:43 +03:00
Daniel-GrunbergerCA
beb4062bb1 update tag 2021-10-25 09:29:43 +03:00
David Wertenteil
5d4cd4acdc Merge pull request #188 from dwertent/master
Use interfaces
2021-10-25 09:21:43 +03:00
dwertent
aec8198131 adding score to interface 2021-10-25 08:41:15 +03:00
dwertent
0a850e47df use interfaces 2021-10-24 17:51:03 +03:00
dwertent
4f466d517a fixed junit counter 2021-10-21 16:05:51 +03:00
Rotem Refael
548201c256 Merge pull request #185 from dwertent/master
Update readme with new featurs
2021-10-21 13:46:33 +03:00
dwertent
a54e5d9f8b update readme with new featurs 2021-10-21 13:25:41 +03:00
dwertent
536257afa1 fixed version in build.py 2021-10-21 12:48:52 +03:00
dwertent
d194dd173f fixed image and entrypoint 2021-10-21 11:32:20 +03:00
33 changed files with 577 additions and 401 deletions

View File

@@ -98,15 +98,21 @@ kubescape scan framework nsa --submit
kubescape scan framework mitre --submit
```
* Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armo.cloud/docs/controls)
```
kubescape scan control "Privileged container"
```
* Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
```
kubescape scan framework nsa *.yaml
```
* Scan `yaml`/`json` files from url
* Scan kubernetes manifest files from a public github repository
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
kubescape scan framework nsa https://github.com/armosec/kubescape
```
* Output in `json` format
@@ -193,7 +199,13 @@ go build -o kubescape .
4. Enjoy :zany_face:
## How to build in Docker
## Docker Support
### Official Docker image
```
quay.io/armosec/kubescape
```
### Build your own Docker image
1. Clone Project
```

View File

@@ -41,7 +41,7 @@ def main():
# Set some variables
packageName = getPackageName()
buildUrl = "github.com/armosec/kubescape/cmd.BuildNumber"
buildUrl = "github.com/armosec/kubescape/clihandler/cmd.BuildNumber"
releaseVersion = os.getenv("RELEASE")
ArmoBEServer = os.getenv("ArmoBEServer")
ArmoERServer = os.getenv("ArmoERServer")

View File

@@ -24,4 +24,4 @@ COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
# # Download the frameworks. Use the "--use-default" flag when running kubescape
# RUN kubescape download framework nsa && kubescape download framework mitre
CMD ["kubescape"]
ENTRYPOINT ["kubescape"]

View File

@@ -109,8 +109,10 @@ func ClusterConfigSetup(scanInfo *ScanInfo, k8s *k8sinterface.KubernetesApi, beA
return NewEmptyConfig() // local - Delete local config & Do not send report
}
if scanInfo.Local {
scanInfo.Submit = false
return NewEmptyConfig() // local - Do not send report
}
scanInfo.Submit = true
return clusterConfig // submit/default - Submit report
}
@@ -158,6 +160,7 @@ func (c *ClusterConfig) GetDefaultNS() string { return c.defau
func (c *ClusterConfig) GetBackendAPI() getter.IBackend { return c.backendAPI }
func (c *ClusterConfig) GenerateURL() {
message := "Checkout for more cool features: "
u := url.URL{}
u.Scheme = "https"
@@ -165,9 +168,8 @@ func (c *ClusterConfig) GenerateURL() {
if c.configObj == nil {
return
}
message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL())
if c.configObj.CustomerAdminEMail != "" {
InfoTextDisplay(os.Stdout, message+"\n")
InfoTextDisplay(os.Stdout, "\n\n"+message+u.String()+"\n\n")
return
}
u.Path = "account/sign-up"
@@ -176,8 +178,7 @@ func (c *ClusterConfig) GenerateURL() {
q.Add("customerGUID", c.configObj.CustomerGUID)
u.RawQuery = q.Encode()
InfoTextDisplay(os.Stdout, message+"\n")
InfoTextDisplay(os.Stdout, "\n\n"+message+u.String()+"\n\n")
}
func (c *ClusterConfig) GetCustomerGUID() string {

View File

@@ -18,7 +18,7 @@ type DownloadReleasedPolicy struct {
func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
return &DownloadReleasedPolicy{
gs: gitregostore.InitDefaultGitRegoStore(),
gs: gitregostore.InitDefaultGitRegoStore(-1),
}
}

View File

@@ -35,7 +35,10 @@ func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, e
return nil, err
}
err = json.Unmarshal(f, control)
if err = json.Unmarshal(f, control); err != nil {
return control, err
}
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
return nil, fmt.Errorf("control from file not matching")
}
@@ -50,7 +53,10 @@ func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framew
return nil, err
}
err = json.Unmarshal(f, framework)
if err = json.Unmarshal(f, framework); err != nil {
return framework, err
}
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
return nil, fmt.Errorf("framework from file not matching")
}

View File

@@ -3,7 +3,6 @@ package cautils
import (
"path/filepath"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
)
@@ -84,22 +83,3 @@ func (scanInfo *ScanInfo) setOutputFile() {
func (scanInfo *ScanInfo) ScanRunningCluster() bool {
return len(scanInfo.InputPatterns) == 0
}
func (scanInfo *ScanInfo) SetClusterConfig() (IClusterConfig, *k8sinterface.KubernetesApi) {
var clusterConfig IClusterConfig
var k8s *k8sinterface.KubernetesApi
if !scanInfo.ScanRunningCluster() {
k8sinterface.ConnectedToCluster = false
clusterConfig = NewEmptyConfig()
} else {
k8s = k8sinterface.NewKubernetesApi()
// setup cluster config
clusterConfig = ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector())
}
return clusterConfig, k8s
}
// func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool {
// _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
// return err == nil
// }

View File

@@ -31,7 +31,7 @@ var controlCmd = &cobra.Command{
scanInfo.PolicyIdentifier.Kind = reporthandling.KindControl
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.CliSetup(scanInfo)
err := clihandler.CliSetup(&scanInfo)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)

View File

@@ -54,7 +54,7 @@ var frameworkCmd = &cobra.Command{
}
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.CliSetup(scanInfo)
err := clihandler.CliSetup(&scanInfo)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)

View File

@@ -25,21 +25,21 @@ func GetLatestVersion() (string, error) {
latestVersion := "https://api.github.com/repos/armosec/kubescape/releases/latest"
resp, err := http.Get(latestVersion)
if err != nil {
return "", fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestVersion, err.Error())
return "unknown", fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestVersion, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode < 200 || 301 < resp.StatusCode {
return "", fmt.Errorf("failed to download file, status code: %s", resp.Status)
return "unknown", fmt.Errorf("failed to download file, status code: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body from '%s', reason: %s", latestVersion, err.Error())
return "unknown", fmt.Errorf("failed to read response body from '%s', reason: %s", latestVersion, err.Error())
}
var data map[string]interface{}
err = json.Unmarshal(body, &data)
if err != nil {
return "", fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestVersion, err.Error())
return "unknown", fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestVersion, err.Error())
}
return fmt.Sprintf("%v", data["tag_name"]), nil
}

View File

@@ -6,9 +6,12 @@ import (
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/opaprocessor"
"github.com/armosec/kubescape/policyhandler"
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/kubescape/resultshandling"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/reporter"
@@ -23,25 +26,77 @@ type CLIHandler struct {
var SupportedFrameworks = []string{"nsa", "mitre"}
var ValidFrameworks = strings.Join(SupportedFrameworks, ", ")
func CliSetup(scanInfo cautils.ScanInfo) error {
type componentInterfaces struct {
clusterConfig cautils.IClusterConfig
resourceHandler resourcehandler.IResourceHandler
report reporter.IReport
printerHandler printer.IPrinter
}
clusterConfig, k8s := scanInfo.SetClusterConfig()
func getReporter(scanInfo *cautils.ScanInfo) reporter.IReport {
if !scanInfo.Submit {
return reporter.NewReportMock()
}
if !scanInfo.FrameworkScan {
return reporter.NewReportMock()
}
return reporter.NewReportEventReceiver()
}
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
var resourceHandler resourcehandler.IResourceHandler
var clusterConfig cautils.IClusterConfig
var reportHandler reporter.IReport
if !scanInfo.ScanRunningCluster() {
k8sinterface.ConnectedToCluster = false
clusterConfig = cautils.NewEmptyConfig()
// load fom file
resourceHandler = resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns)
// set mock report (do not send report)
reportHandler = reporter.NewReportMock()
} else {
k8s := k8sinterface.NewKubernetesApi()
resourceHandler = resourcehandler.NewK8sResourceHandler(k8s, scanInfo.ExcludedNamespaces)
clusterConfig = cautils.ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector())
// setup reporter
reportHandler = getReporter(scanInfo)
}
// setup printer
printerHandler := printer.GetPrinter(scanInfo.Format)
printerHandler.SetWriter(scanInfo.Output)
return componentInterfaces{
clusterConfig: clusterConfig,
resourceHandler: resourceHandler,
report: reportHandler,
printerHandler: printerHandler,
}
}
func CliSetup(scanInfo *cautils.ScanInfo) error {
interfaces := getInterfaces(scanInfo)
processNotification := make(chan *cautils.OPASessionObj)
reportResults := make(chan *cautils.OPASessionObj)
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s)
if err := clusterConfig.SetConfig(scanInfo.Account); err != nil {
if err := interfaces.clusterConfig.SetConfig(scanInfo.Account); err != nil {
fmt.Println(err)
}
cautils.ClusterName = clusterConfig.GetClusterName()
cautils.CustomerGUID = clusterConfig.GetCustomerGUID()
cautils.ClusterName = interfaces.clusterConfig.GetClusterName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.clusterConfig.GetCustomerGUID() // TODO - Deprecated
interfaces.report.SetClusterName(interfaces.clusterConfig.GetClusterName())
interfaces.report.SetCustomerGUID(interfaces.clusterConfig.GetCustomerGUID())
// cli handler setup
go func() {
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
cli := NewCLIHandler(policyHandler, scanInfo)
if err := cli.Scan(); err != nil {
fmt.Println(err)
@@ -55,13 +110,11 @@ func CliSetup(scanInfo cautils.ScanInfo) error {
opaprocessorObj.ProcessRulesListenner()
}()
resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output))
resultsHandling := resultshandling.NewResultsHandler(&reportResults, interfaces.report, interfaces.printerHandler)
score := resultsHandling.HandleResults(scanInfo)
// print report url
if scanInfo.FrameworkScan {
clusterConfig.GenerateURL()
}
interfaces.clusterConfig.GenerateURL()
adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100
if score < adjustedFailThreshold {
@@ -71,9 +124,9 @@ func CliSetup(scanInfo cautils.ScanInfo) error {
return nil
}
func NewCLIHandler(policyHandler *policyhandler.PolicyHandler, scanInfo cautils.ScanInfo) *CLIHandler {
func NewCLIHandler(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) *CLIHandler {
return &CLIHandler{
scanInfo: &scanInfo,
scanInfo: scanInfo,
policyHandler: policyHandler,
}
}

View File

@@ -72,10 +72,8 @@ metadata:
data:
config.json: |
{
"adminMail": "",
"customerGUID": "<MyGUID>",
"clusterName": "<MyK8sClusterName>",
"invitationParam": ""
"customerGUID": <MyGUID>,
"clusterName": <MyK8sClusterName>
}
---
@@ -104,12 +102,11 @@ spec:
spec:
containers:
- name: kubescape
image: quay.io/armosec/kubescape:latset
image: quay.io/armosec/kubescape:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args:
- kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
#- kubescape scan framework nsa --use-default --exclude-namespaces kube-system,kube-public
- kubescape scan framework nsa --submit
volumeMounts:
- name: kubescape-config-volume
mountPath: /root/.kubescape/config.json

4
go.mod
View File

@@ -4,8 +4,8 @@ go 1.17
require (
github.com/armosec/armoapi-go v0.0.8
github.com/armosec/k8s-interface v0.0.5
github.com/armosec/opa-utils v0.0.13
github.com/armosec/k8s-interface v0.0.8
github.com/armosec/opa-utils v0.0.18
github.com/armosec/utils-go v0.0.3
github.com/briandowns/spinner v1.16.0
github.com/enescakir/emoji v1.0.0

7
go.sum
View File

@@ -87,10 +87,11 @@ github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qm
github.com/armosec/armoapi-go v0.0.7/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.8 h1:JPa9rZynuE2RucamDh6dsy/sjCScmWDsyt1zagJFCDo=
github.com/armosec/armoapi-go v0.0.8/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/k8s-interface v0.0.5 h1:DWQXZNMSsYQeLQ6xpB21ueFMR9oFnz28iWQTNn31TAk=
github.com/armosec/k8s-interface v0.0.5/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/opa-utils v0.0.13 h1:QkmmYX0lzC7ZNGetyD8ysRKQHgJhjMfvRUW2cp+hz2o=
github.com/armosec/opa-utils v0.0.13/go.mod h1:E0mFTVx+4BYAVvO2hxWnIniv/IZIogRCak8BkKd7KK4=
github.com/armosec/k8s-interface v0.0.8 h1:Eo3Qen4yFXxzVem49FNeij2ckyzHSAJ0w6PZMaSEIm8=
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/opa-utils v0.0.18 h1:1hL5v2KCD8yStuwzul+gq1zg9+RCV9N3kHoRepKnrg0=
github.com/armosec/opa-utils v0.0.18/go.mod h1:E0mFTVx+4BYAVvO2hxWnIniv/IZIogRCak8BkKd7KK4=
github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
github.com/armosec/utils-go v0.0.3 h1:uyQI676yRciQM0sSN9uPoqHkbspTxHO0kmzXhBeE/xU=
github.com/armosec/utils-go v0.0.3/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=

View File

@@ -8,7 +8,6 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/exceptions"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/score"
"github.com/armosec/k8s-interface/k8sinterface"
@@ -226,17 +225,6 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe
return results, nil
}
func (opap *OPAProcessor) updateScore() {
if !k8sinterface.ConnectedToCluster {
return
}
// calculate score
s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath)
s.Calculate(opap.PostureReport.FrameworkReports)
}
func (opap *OPAProcessor) updateResults() {
for f := range opap.PostureReport.FrameworkReports {
// set exceptions

View File

@@ -4,30 +4,25 @@ import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
)
var supportedFrameworks = []reporthandling.PolicyIdentifier{
{Kind: "Framework", Name: "nsa"},
{Kind: "Framework", Name: "mitre"},
}
// PolicyHandler -
type PolicyHandler struct {
k8s *k8sinterface.KubernetesApi
resourceHandler resourcehandler.IResourceHandler
// we are listening on this chan in opaprocessor/processorhandler.go/ProcessRulesListenner func
processPolicy *chan *cautils.OPASessionObj
getters *cautils.Getters
}
// CreatePolicyHandler Create ws-handler obj
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, k8s *k8sinterface.KubernetesApi) *PolicyHandler {
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, resourceHandler resourcehandler.IResourceHandler) *PolicyHandler {
return &PolicyHandler{
k8s: k8s,
processPolicy: processPolicy,
resourceHandler: resourceHandler,
processPolicy: processPolicy,
}
}
@@ -82,16 +77,7 @@ func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.Pol
}
func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
var k8sResources *cautils.K8SResources
var err error
if k8sinterface.ConnectedToCluster { // TODO - use interface
if opaSessionObj.PostureReport.ClusterAPIServerInfo, err = policyHandler.k8s.KubernetesClient.Discovery().ServerVersion(); err != nil {
cautils.ErrorDisplay(fmt.Sprintf("Failed to discover API server inforamtion: %v", err))
}
k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, &notification.Designators, scanInfo.ExcludedNamespaces)
} else {
k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
}
return k8sResources, err
opaSessionObj.PostureReport.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
return policyHandler.resourceHandler.GetResources(opaSessionObj.Frameworks, &notification.Designators)
}

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"bytes"
@@ -8,7 +8,9 @@ import (
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/workloadinterface"
"k8s.io/apimachinery/pkg/version"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
@@ -29,11 +31,22 @@ const (
JSON_FILE_FORMAT FileFormat = "json"
)
func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Framework, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
// FileResourceHandler handle resources from files and URLs
type FileResourceHandler struct {
inputPatterns []string
}
func NewFileResourceHandler(inputPatterns []string) *FileResourceHandler {
return &FileResourceHandler{
inputPatterns: inputPatterns,
}
}
func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
workloads := []k8sinterface.IWorkload{}
// load resource from local file system
w, err := loadResourcesFromFiles(scanInfo.InputPatterns)
w, err := loadResourcesFromFiles(fileHandler.inputPatterns)
if err != nil {
return nil, err
}
@@ -42,7 +55,7 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
}
// load resources from url
w, err = loadResourcesFromUrl(scanInfo.InputPatterns)
w, err = loadResourcesFromUrl(fileHandler.inputPatterns)
if err != nil {
return nil, err
}
@@ -59,7 +72,7 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
// build resources map
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads>
k8sResources := setResourceMap(frameworks)
k8sResources := setResourceMap(frameworks) // TODO - support designators
// save only relevant resources
for i := range allResources {
@@ -72,6 +85,10 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
}
func (fileHandler *FileResourceHandler) GetClusterAPIServerInfo() *version.Info {
return nil
}
func loadResourcesFromFiles(inputPatterns []string) ([]k8sinterface.IWorkload, error) {
files, errs := listFiles(inputPatterns)
if len(errs) > 0 {

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"fmt"

View File

@@ -1,6 +1,7 @@
package policyhandler
package resourcehandler
import (
"context"
"fmt"
"strings"
@@ -15,12 +16,23 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8slabels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/dynamic"
)
const SelectAllResources = "*"
type K8sResourceHandler struct {
k8s *k8sinterface.KubernetesApi
excludedNamespaces string // excluded namespaces (separated by comma)
}
func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator, excludedNamespaces string) (*cautils.K8SResources, error) {
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, excludedNamespaces string) *K8sResourceHandler {
return &K8sResourceHandler{
k8s: k8s,
excludedNamespaces: excludedNamespaces,
}
}
func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
// get k8s resources
cautils.ProgressTextDisplay("Accessing Kubernetes objects")
@@ -31,7 +43,7 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
// pull k8s recourses
if err := policyHandler.pullResources(k8sResourcesMap, namespace, labels, excludedNamespaces); err != nil {
if err := k8sHandler.pullResources(k8sResourcesMap, namespace, labels, k8sHandler.excludedNamespaces); err != nil {
return k8sResourcesMap, err
}
@@ -39,13 +51,21 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.
return k8sResourcesMap, nil
}
func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
clusterAPIServerInfo, err := k8sHandler.k8s.KubernetesClient.Discovery().ServerVersion()
if err != nil {
cautils.ErrorDisplay(fmt.Sprintf("Failed to discover API server information: %v", err))
return nil
}
return clusterAPIServerInfo
}
func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
var errs error
for groupResource := range *k8sResources {
apiGroup, apiVersion, resource := k8sinterface.StringToResourceGroup(groupResource)
gvr := schema.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: resource}
result, err := policyHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
result, err := k8sHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
if err != nil {
// handle error
if errs == nil {
@@ -61,7 +81,7 @@ func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResou
return errs
}
func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
// set labels
listOptions := metav1.ListOptions{}
@@ -76,13 +96,13 @@ func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVer
// set dynamic object
var clientResource dynamic.ResourceInterface
if namespace != "" && k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
} else {
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource)
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource)
}
// list resources
result, err := clientResource.List(policyHandler.k8s.Context, listOptions)
result, err := clientResource.List(context.Background(), listOptions)
if err != nil {
return nil, fmt.Errorf("failed to get resource: %v, namespace: %s, labelSelector: %v, reason: %s", resource, namespace, listOptions.LabelSelector, err.Error())
}

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"github.com/armosec/kubescape/cautils"

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"github.com/armosec/k8s-interface/k8sinterface"

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"encoding/json"

View File

@@ -0,0 +1,13 @@
package resourcehandler
import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"k8s.io/apimachinery/pkg/version"
)
type IResourceHandler interface {
GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error)
GetClusterAPIServerInfo() *version.Info
}

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"bytes"

View File

@@ -0,0 +1,34 @@
package printer
import (
"encoding/json"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
)
type JsonPrinter struct {
writer *os.File
}
func NewJsonPrinter() *JsonPrinter {
return &JsonPrinter{}
}
func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
jsonPrinter.writer = getWriter(outputFile)
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Printf("\nFinal score: %d", int(score*100))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
postureReportStr, err := json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
jsonPrinter.writer.Write(postureReportStr)
}

View File

@@ -3,10 +3,42 @@ package printer
import (
"encoding/xml"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
)
type JunitPrinter struct {
writer *os.File
}
func NewJunitPrinter() *JunitPrinter {
return &JunitPrinter{}
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = getWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Printf("\nFinal score: %d", int(score*100))
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
junitPrinter.writer.Write(postureReportStr)
}
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite `xml:"testsuite"`
@@ -74,9 +106,9 @@ func convertPostureReportToJunitResult(postureResult *reporthandling.PostureRepo
testCase.Time = "0"
if 0 < len(controlReports.RuleReports[0].RuleResponses) {
testCase.Resources = framework.GetNumberOfResources()
testCase.Excluded = framework.GetNumberOfWarningResources()
testCase.Failed = framework.GetNumberOfFailedResources()
testCase.Resources = controlReports.GetNumberOfResources()
testCase.Excluded = controlReports.GetNumberOfWarningResources()
testCase.Failed = controlReports.GetNumberOfFailedResources()
failure := JUnitFailure{}
failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {

View File

@@ -0,0 +1,219 @@
package printer
import (
"fmt"
"os"
"sort"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
)
type PrettyPrinter struct {
writer *os.File
summary Summary
sortedControlNames []string
frameworkSummary ControlSummary
}
func NewPrettyPrinter() *PrettyPrinter {
return &PrettyPrinter{
summary: NewSummary(),
}
}
func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
// score := calculatePostureScore(opaSessionObj.PostureReport)
printer.summarySetup(opaSessionObj.PostureReport)
printer.printResults()
printer.printSummaryTable()
// return score
}
func (printer *PrettyPrinter) SetWriter(outputFile string) {
printer.writer = getWriter(outputFile)
}
func (printer *PrettyPrinter) Score(score float32) {
}
func (printer *PrettyPrinter) summarySetup(postureReport *reporthandling.PostureReport) {
for _, fr := range postureReport.FrameworkReports {
printer.frameworkSummary = ControlSummary{
TotalResources: fr.GetNumberOfResources(),
TotalFailed: fr.GetNumberOfFailedResources(),
TotalWarnign: fr.GetNumberOfWarningResources(),
}
for _, cr := range fr.ControlReports {
if len(cr.RuleReports) == 0 {
continue
}
workloadsSummary := listResultSummary(cr.RuleReports)
printer.summary[cr.Name] = ControlSummary{
TotalResources: cr.GetNumberOfResources(),
TotalFailed: cr.GetNumberOfFailedResources(),
TotalWarnign: cr.GetNumberOfWarningResources(),
FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed),
ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude),
Description: cr.Description,
Remediation: cr.Remediation,
ListInputKinds: cr.ListControlsInputKinds(),
}
}
}
printer.sortedControlNames = printer.getSortedControlsNames()
}
func (printer *PrettyPrinter) printResults() {
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
printer.printResources(&controlSummary)
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
}
}
}
func (printer *PrettyPrinter) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(printer.writer, "Summary - ")
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarnign)
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarnign)
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(printer.writer, "\n")
}
func (printer *PrettyPrinter) printTitle(controlName string, controlSummary *ControlSummary) {
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed != 0 {
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
} else if controlSummary.TotalWarnign != 0 {
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
} else {
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
}
func (printer *PrettyPrinter) printResources(controlSummary *ControlSummary) {
if len(controlSummary.FailedWorkloads) > 0 {
cautils.FailureDisplay(printer.writer, "Failed:\n")
printer.printGroupedResources(controlSummary.FailedWorkloads)
}
if len(controlSummary.ExcludedWorkloads) > 0 {
cautils.WarningDisplay(printer.writer, "Excluded:\n")
printer.printGroupedResources(controlSummary.ExcludedWorkloads)
}
}
func (printer *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
indent := INDENT
for ns, rsc := range workloads {
preIndent := indent
if ns != "" {
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
}
preIndent2 := indent
for r := range rsc {
indent += indent
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
indent = preIndent2
}
indent = preIndent
}
}
func generateRow(control string, cs ControlSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
if cs.TotalResources != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% success"}
}
func percentage(big, small int) int {
if big == 0 {
if small == 0 {
return 100
}
return 0
}
return int(float64(float64(big-small)/float64(big)) * 100)
}
func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string {
// Control name | # failed resources | all resources | % success
row := []string{}
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
row = append(row, fmt.Sprintf("%d", sumFailed))
row = append(row, fmt.Sprintf("%d", sumWarning))
row = append(row, fmt.Sprintf("%d", sumTotal))
if sumTotal != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func (printer *PrettyPrinter) printSummaryTable() {
summaryTable := tablewriter.NewWriter(printer.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
summaryTable.SetColumnAlignment(alignments)
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
}
summaryTable.SetFooter(generateFooter(len(printer.summary), printer.frameworkSummary.TotalFailed, printer.frameworkSummary.TotalWarnign, printer.frameworkSummary.TotalResources))
summaryTable.Render()
}
func (printer *PrettyPrinter) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(printer.summary))
for k := range printer.summary {
controlNames = append(controlNames, k)
}
sort.Strings(controlNames)
return controlNames
}
func getWriter(outputFile string) *os.File {
os.Remove(outputFile)
if outputFile != "" {
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file")
return os.Stdout
}
return f
}
return os.Stdout
}

View File

@@ -1,17 +1,7 @@
package printer
import (
"encoding/json"
"encoding/xml"
"fmt"
"os"
"sort"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
)
var INDENT = " "
@@ -19,253 +9,24 @@ var INDENT = " "
const EmptyPercentage = "NaN"
const (
PrettyPrinter string = "pretty-printer"
JsonPrinter string = "json"
PrettyFormat string = "pretty-printer"
JsonFormat string = "json"
JunitResultPrinter string = "junit"
)
type Printer struct {
writer *os.File
summary Summary
sortedControlNames []string
printerType string
frameworkSummary ControlSummary
type IPrinter interface {
ActionPrint(opaSessionObj *cautils.OPASessionObj)
SetWriter(outputFile string)
Score(score float32)
}
func NewPrinter(printerType, outputFile string) *Printer {
return &Printer{
summary: NewSummary(),
writer: getWriter(outputFile),
printerType: printerType,
func GetPrinter(printFormat string) IPrinter {
switch printFormat {
case JsonFormat:
return NewJsonPrinter()
case JunitResultPrinter:
return NewJunitPrinter()
default:
return NewPrettyPrinter()
}
}
func calculatePostureScore(postureReport *reporthandling.PostureReport) float32 {
totalResources := 0
totalFailed := 0
for _, frameworkReport := range postureReport.FrameworkReports {
totalFailed += frameworkReport.GetNumberOfFailedResources()
totalResources += frameworkReport.GetNumberOfResources()
}
if totalResources == 0 {
return float32(0)
}
return (float32(totalResources) - float32(totalFailed)) / float32(totalResources)
}
func (printer *Printer) ActionPrint(opaSessionObj *cautils.OPASessionObj) float32 {
score := calculatePostureScore(opaSessionObj.PostureReport)
if printer.printerType == PrettyPrinter {
printer.SummarySetup(opaSessionObj.PostureReport)
printer.PrintResults()
printer.PrintSummaryTable()
} else if printer.printerType == JsonPrinter {
postureReportStr, err := json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
printer.writer.Write(postureReportStr)
fmt.Printf("\nFinal score: %d\n", int(score*100))
} else if printer.printerType == JunitResultPrinter {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
printer.writer.Write(postureReportStr)
fmt.Printf("\nFinal score: %d\n", int(score*100))
} else if !cautils.IsSilent() {
fmt.Println("unknown output printer")
os.Exit(1)
}
return score
}
func (printer *Printer) SummarySetup(postureReport *reporthandling.PostureReport) {
for _, fr := range postureReport.FrameworkReports {
printer.frameworkSummary = ControlSummary{
TotalResources: fr.GetNumberOfResources(),
TotalFailed: fr.GetNumberOfFailedResources(),
TotalWarnign: fr.GetNumberOfWarningResources(),
}
for _, cr := range fr.ControlReports {
if len(cr.RuleReports) == 0 {
continue
}
workloadsSummary := listResultSummary(cr.RuleReports)
printer.summary[cr.Name] = ControlSummary{
TotalResources: cr.GetNumberOfResources(),
TotalFailed: cr.GetNumberOfFailedResources(),
TotalWarnign: cr.GetNumberOfWarningResources(),
FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed),
ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude),
Description: cr.Description,
Remediation: cr.Remediation,
ListInputKinds: cr.ListControlsInputKinds(),
}
}
}
printer.sortedControlNames = printer.getSortedControlsNames()
}
func (printer *Printer) PrintResults() {
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
printer.printResources(&controlSummary)
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
}
}
}
func (printer *Printer) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(printer.writer, "Summary - ")
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarnign)
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarnign)
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(printer.writer, "\n")
}
func (printer *Printer) printTitle(controlName string, controlSummary *ControlSummary) {
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed != 0 {
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
} else if controlSummary.TotalWarnign != 0 {
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
} else {
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
}
func (printer *Printer) printResources(controlSummary *ControlSummary) {
if len(controlSummary.FailedWorkloads) > 0 {
cautils.FailureDisplay(printer.writer, "Failed:\n")
printer.printGroupedResources(controlSummary.FailedWorkloads)
}
if len(controlSummary.ExcludedWorkloads) > 0 {
cautils.WarningDisplay(printer.writer, "Excluded:\n")
printer.printGroupedResources(controlSummary.ExcludedWorkloads)
}
}
func (printer *Printer) printGroupedResources(workloads map[string][]WorkloadSummary) {
indent := INDENT
for ns, rsc := range workloads {
preIndent := indent
if ns != "" {
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
}
preIndent2 := indent
for r := range rsc {
indent += indent
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
indent = preIndent2
}
indent = preIndent
}
}
func (printer *Printer) PrintUrl(url string) {
cautils.InfoTextDisplay(printer.writer, url)
}
func generateRow(control string, cs ControlSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
if cs.TotalResources != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% success"}
}
func percentage(big, small int) int {
if big == 0 {
if small == 0 {
return 100
}
return 0
}
return int(float64(float64(big-small)/float64(big)) * 100)
}
func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string {
// Control name | # failed resources | all resources | % success
row := []string{}
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
row = append(row, fmt.Sprintf("%d", sumFailed))
row = append(row, fmt.Sprintf("%d", sumWarning))
row = append(row, fmt.Sprintf("%d", sumTotal))
if sumTotal != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func (printer *Printer) PrintSummaryTable() {
summaryTable := tablewriter.NewWriter(printer.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
summaryTable.SetColumnAlignment(alignments)
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
}
summaryTable.SetFooter(generateFooter(len(printer.summary), printer.frameworkSummary.TotalFailed, printer.frameworkSummary.TotalWarnign, printer.frameworkSummary.TotalResources))
summaryTable.Render()
}
func (printer *Printer) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(printer.summary))
for k := range printer.summary {
controlNames = append(controlNames, k)
}
sort.Strings(controlNames)
return controlNames
}
func getWriter(outputFile string) *os.File {
os.Remove(outputFile)
if outputFile != "" {
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file")
return os.Stdout
}
return f
}
return os.Stdout
}

View File

@@ -0,0 +1,11 @@
package printer
import (
"github.com/armosec/kubescape/cautils"
)
type SilentPrinter struct {
}
func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
}

View File

@@ -0,0 +1,17 @@
package reporter
import "github.com/armosec/kubescape/cautils"
type ReportMock struct {
}
func NewReportMock() *ReportMock {
return &ReportMock{}
}
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) {}
func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
}
func (reportMock *ReportMock) SetClusterName(clusterName string) {
}

View File

@@ -5,47 +5,55 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
)
type IReport interface {
ActionSendReport(opaSessionObj *cautils.OPASessionObj)
SetCustomerGUID(customerGUID string)
SetClusterName(clusterName string)
}
type ReportEventReceiver struct {
httpClient http.Client
host url.URL
httpClient http.Client
clusterName string
customerGUID string
}
func NewReportEventReceiver() *ReportEventReceiver {
hostURL := initEventReceiverURL()
return &ReportEventReceiver{
httpClient: http.Client{},
host: *hostURL,
}
}
func (report *ReportEventReceiver) ActionSendReportListenner(opaSessionObj *cautils.OPASessionObj) {
if cautils.CustomerGUID == "" {
return
}
//Add score
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) {
// Remove data before reporting
keepFields := []string{"kind", "apiVersion", "metadata"}
keepMetadataFields := []string{"name", "namespace", "labels"}
opaSessionObj.PostureReport.RemoveData(keepFields, keepMetadataFields)
if err := report.Send(opaSessionObj.PostureReport); err != nil {
if err := report.send(opaSessionObj.PostureReport); err != nil {
fmt.Println(err)
}
}
func (report *ReportEventReceiver) Send(postureReport *reporthandling.PostureReport) error {
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
report.customerGUID = customerGUID
}
func (report *ReportEventReceiver) SetClusterName(clusterName string) {
report.clusterName = clusterName
}
func (report *ReportEventReceiver) send(postureReport *reporthandling.PostureReport) error {
reqBody, err := json.Marshal(*postureReport)
if err != nil {
return fmt.Errorf("in 'Send' failed to json.Marshal, reason: %v", err)
}
host := hostToString(&report.host, postureReport.ReportID)
host := hostToString(report.initEventReceiverURL(), postureReport.ReportID)
req, err := http.NewRequest("POST", host, bytes.NewReader(reqBody))
if err != nil {

View File

@@ -7,7 +7,6 @@ import (
"net/url"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/gofrs/uuid"
)
@@ -33,15 +32,15 @@ func httpRespToString(resp *http.Response) (string, error) {
return strBuilder.String(), err
}
func initEventReceiverURL() *url.URL {
func (report *ReportEventReceiver) initEventReceiverURL() *url.URL {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.FromStringOrNil(cautils.CustomerGUID).String())
q.Add("clusterName", cautils.ClusterName)
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
q.Add("clusterName", report.clusterName)
urlObj.RawQuery = q.Encode()

View File

@@ -4,15 +4,16 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
)
type ResultsHandler struct {
opaSessionObj *chan *cautils.OPASessionObj
reporterObj *reporter.ReportEventReceiver
printerObj *printer.Printer
reporterObj reporter.IReport
printerObj printer.IPrinter
}
func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj *reporter.ReportEventReceiver, printerObj *printer.Printer) *ResultsHandler {
func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj reporter.IReport, printerObj printer.IPrinter) *ResultsHandler {
return &ResultsHandler{
opaSessionObj: opaSessionObj,
reporterObj: reporterObj,
@@ -20,16 +21,36 @@ func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj *
}
}
func (resultsHandler *ResultsHandler) HandleResults(scanInfo cautils.ScanInfo) float32 {
func (resultsHandler *ResultsHandler) HandleResults(scanInfo *cautils.ScanInfo) float32 {
opaSessionObj := <-*resultsHandler.opaSessionObj
score := resultsHandler.printerObj.ActionPrint(opaSessionObj)
resultsHandler.printerObj.ActionPrint(opaSessionObj)
// Don't send report for control scan
if scanInfo.FrameworkScan { // TODO - use interface for ActionSendReportListenner
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
}
resultsHandler.reporterObj.ActionSendReport(opaSessionObj)
// TODO - get score from table
score := CalculatePostureScore(opaSessionObj.PostureReport)
resultsHandler.printerObj.Score(score)
return score
}
// CalculatePostureScore calculate final score
func CalculatePostureScore(postureReport *reporthandling.PostureReport) float32 {
lowestScore := float32(100)
for _, frameworkReport := range postureReport.FrameworkReports {
totalFailed := frameworkReport.GetNumberOfFailedResources()
totalResources := frameworkReport.GetNumberOfResources()
frameworkScore := float32(0)
if float32(totalResources) > 0 {
frameworkScore = (float32(totalResources) - float32(totalFailed)) / float32(totalResources)
}
if lowestScore > frameworkScore {
lowestScore = frameworkScore
}
}
return lowestScore
}