diff --git a/README.md b/README.md index ca0c88af..b5dc385d 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,12 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle
+# CLI Interface: +# Web Interface: + + # TL;DR ## Install: ``` diff --git a/cmd/go.mod b/cmd/go.mod index 8ef4e8bd..6e13399b 100644 --- a/cmd/go.mod +++ b/cmd/go.mod @@ -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.118 + github.com/armosec/opa-utils v0.0.120 github.com/armosec/rbac-utils v0.0.14 github.com/google/uuid v1.3.0 github.com/mattn/go-isatty v0.0.14 diff --git a/cmd/go.sum b/cmd/go.sum index ab369692..9e212048 100644 --- a/cmd/go.sum +++ b/cmd/go.sum @@ -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.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0= -github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ= +github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k= +github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ= github.com/armosec/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= diff --git a/core/cautils/datastructures.go b/core/cautils/datastructures.go index e144fa31..f937ed18 100644 --- a/core/cautils/datastructures.go +++ b/core/cautils/datastructures.go @@ -4,35 +4,45 @@ import ( "github.com/armosec/armoapi-go/armotypes" "github.com/armosec/k8s-interface/workloadinterface" "github.com/armosec/opa-utils/reporthandling" + apis "github.com/armosec/opa-utils/reporthandling/apis" "github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults" reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2" + v2 "github.com/armosec/opa-utils/reporthandling/v2" ) // K8SResources map[//][] type K8SResources map[string][]string +type ArmoResources map[string][]string type OPASessionObj struct { - K8SResources *K8SResources // input k8s objects - Policies []reporthandling.Framework // list of frameworks to scan - AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[] - ResourcesResult map[string]resourcesresults.Result // resources scan results, map[] - PostureReport *reporthandling.PostureReport // scan results v1 - Remove - Report *reporthandlingv2.PostureReport // scan results v2 - Remove - Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results - RegoInputData RegoInputData // input passed to rgo for scanning. map[][] + K8SResources *K8SResources // input k8s objects + ArmoResource *ArmoResources // input ARMO objects + Policies []reporthandling.Framework // list of frameworks to scan + AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[] + ResourcesResult map[string]resourcesresults.Result // resources scan results, map[] + PostureReport *reporthandling.PostureReport // scan results v1 - Remove + Report *reporthandlingv2.PostureReport // scan results v2 - Remove + Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results + RegoInputData RegoInputData // input passed to rgo for scanning. map[][] + Metadata *reporthandlingv2.Metadata + InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo + ResourceToControlsMap map[string][]string // map[] = [] } func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj { return &OPASessionObj{ - Report: &reporthandlingv2.PostureReport{}, - Policies: frameworks, - K8SResources: k8sResources, - AllResources: make(map[string]workloadinterface.IMetadata), - ResourcesResult: make(map[string]resourcesresults.Result), + Report: &reporthandlingv2.PostureReport{}, + Policies: frameworks, + K8SResources: k8sResources, + AllResources: make(map[string]workloadinterface.IMetadata), + ResourcesResult: make(map[string]resourcesresults.Result), + InfoMap: make(map[string]apis.StatusInfo), + ResourceToControlsMap: make(map[string][]string), PostureReport: &reporthandling.PostureReport{ ClusterName: ClusterName, CustomerGUID: CustomerGUID, }, + Metadata: &v2.Metadata{}, } } diff --git a/core/cautils/reportv2tov1.go b/core/cautils/reportv2tov1.go index af19aaf7..ba5e6478 100644 --- a/core/cautils/reportv2tov1.go +++ b/core/cautils/reportv2tov1.go @@ -11,8 +11,7 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) { if len(opaSessionObj.PostureReport.FrameworkReports) > 0 { return // report already converted } - - opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider + // opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider frameworks := []reporthandling.FrameworkReport{} diff --git a/core/cautils/workloadmappingutils.go b/core/cautils/workloadmappingutils.go new file mode 100644 index 00000000..4fa94abe --- /dev/null +++ b/core/cautils/workloadmappingutils.go @@ -0,0 +1,52 @@ +package cautils + +import ( + "strings" + + "github.com/armosec/opa-utils/reporthandling/apis" +) + +var ( + ImageVulnResources = []string{"ImageVulnerabilities"} + HostSensorResources = []string{"KubeletConfiguration", + "KubeletCommandLine", + "OsReleaseFile", + "KernelVersion", + "LinuxSecurityHardeningStatus", + "OpenPortsList", + "LinuxKernelVariables"} + CloudResources = []string{"ClusterDescribe"} +) + +func MapArmoResource(armoResourceMap *ArmoResources, resources []string) []string { + var hostResources []string + for k := range *armoResourceMap { + for _, resource := range resources { + if strings.Contains(k, resource) { + hostResources = append(hostResources, k) + } + } + } + return hostResources +} + +func MapHostResources(armoResourceMap *ArmoResources) []string { + return MapArmoResource(armoResourceMap, HostSensorResources) +} + +func MapImageVulnResources(armoResourceMap *ArmoResources) []string { + return MapArmoResource(armoResourceMap, ImageVulnResources) +} + +func MapCloudResources(armoResourceMap *ArmoResources) []string { + return MapArmoResource(armoResourceMap, CloudResources) +} + +func SetInfoMapForResources(info string, resources []string, errorMap map[string]apis.StatusInfo) { + for _, resource := range resources { + errorMap[resource] = apis.StatusInfo{ + InnerInfo: info, + InnerStatus: apis.StatusSkipped, + } + } +} diff --git a/core/go.mod b/core/go.mod index 941150a2..c96360df 100644 --- a/core/go.mod +++ b/core/go.mod @@ -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.118 + github.com/armosec/opa-utils v0.0.120 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 @@ -25,6 +25,7 @@ require ( k8s.io/api v0.23.4 k8s.io/apimachinery v0.23.4 k8s.io/client-go v0.23.4 + k8s.io/utils v0.0.0-20211116205334-6203023598ed sigs.k8s.io/yaml v1.3.0 ) @@ -116,7 +117,6 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect - k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect sigs.k8s.io/controller-runtime v0.11.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect diff --git a/core/go.sum b/core/go.sum index d279dfff..b645f269 100644 --- a/core/go.sum +++ b/core/go.sum @@ -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.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0= -github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ= +github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k= +github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ= github.com/armosec/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= diff --git a/core/pkg/hostsensorutils/hostsensorgetfrompod.go b/core/pkg/hostsensorutils/hostsensorgetfrompod.go index bdf35d90..ff3cea72 100644 --- a/core/pkg/hostsensorutils/hostsensorgetfrompod.go +++ b/core/pkg/hostsensorutils/hostsensorgetfrompod.go @@ -9,6 +9,7 @@ import ( "github.com/armosec/kubescape/core/cautils/logger" "github.com/armosec/kubescape/core/cautils/logger/helpers" "github.com/armosec/opa-utils/objectsenvelopes/hostsensor" + "github.com/armosec/opa-utils/reporthandling/apis" "sigs.k8s.io/yaml" ) @@ -156,56 +157,77 @@ func (hsh *HostSensorHandler) GetKubeletConfigurations() ([]hostsensor.HostSenso return res, err } -func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) { +func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnvelope, map[string]apis.StatusInfo, error) { res := make([]hostsensor.HostSensorDataEnvelope, 0) + infoMap := make(map[string]apis.StatusInfo) if hsh.DaemonSet == nil { - return res, nil + return res, nil, nil } - + var kcData []hostsensor.HostSensorDataEnvelope + var err error logger.L().Debug("Accessing host scanner") - kcData, err := hsh.GetKubeletConfigurations() + kcData, err = hsh.GetKubeletConfigurations() if err != nil { - return kcData, err + addInfoToMap(KubeletConfiguration, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) // kcData, err = hsh.GetKubeletCommandLine() if err != nil { - return kcData, err + addInfoToMap(KubeletCommandLine, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) // kcData, err = hsh.GetOsReleaseFile() if err != nil { - return kcData, err + addInfoToMap(OsReleaseFile, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) // kcData, err = hsh.GetKernelVersion() if err != nil { - return kcData, err + addInfoToMap(KernelVersion, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) // kcData, err = hsh.GetLinuxSecurityHardeningStatus() if err != nil { - return kcData, err + addInfoToMap(LinuxSecurityHardeningStatus, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) // kcData, err = hsh.GetOpenPortsList() if err != nil { - return kcData, err + addInfoToMap(OpenPortsList, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) // GetKernelVariables kcData, err = hsh.GetKernelVariables() if err != nil { - return kcData, err + addInfoToMap(LinuxKernelVariables, infoMap, err) + logger.L().Warning(err.Error()) + } + if len(kcData) > 0 { + res = append(res, kcData...) } - res = append(res, kcData...) - // finish - logger.L().Debug("Done reading information from host scanner") - return res, nil + return res, infoMap, nil } diff --git a/core/pkg/hostsensorutils/hostsensorinterface.go b/core/pkg/hostsensorutils/hostsensorinterface.go index 9b9d9d9b..71d5e6ef 100644 --- a/core/pkg/hostsensorutils/hostsensorinterface.go +++ b/core/pkg/hostsensorutils/hostsensorinterface.go @@ -1,10 +1,13 @@ package hostsensorutils -import "github.com/armosec/opa-utils/objectsenvelopes/hostsensor" +import ( + "github.com/armosec/opa-utils/objectsenvelopes/hostsensor" + "github.com/armosec/opa-utils/reporthandling/apis" +) type IHostSensor interface { Init() error TearDown() error - CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) + CollectResources() ([]hostsensor.HostSensorDataEnvelope, map[string]apis.StatusInfo, error) GetNamespace() string } diff --git a/core/pkg/hostsensorutils/hostsensormock.go b/core/pkg/hostsensorutils/hostsensormock.go index b4518003..f6f22fa0 100644 --- a/core/pkg/hostsensorutils/hostsensormock.go +++ b/core/pkg/hostsensorutils/hostsensormock.go @@ -2,6 +2,7 @@ package hostsensorutils import ( "github.com/armosec/opa-utils/objectsenvelopes/hostsensor" + "github.com/armosec/opa-utils/reporthandling/apis" ) type HostSensorHandlerMock struct { @@ -15,8 +16,8 @@ func (hshm *HostSensorHandlerMock) TearDown() error { return nil } -func (hshm *HostSensorHandlerMock) CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) { - return []hostsensor.HostSensorDataEnvelope{}, nil +func (hshm *HostSensorHandlerMock) CollectResources() ([]hostsensor.HostSensorDataEnvelope, map[string]apis.StatusInfo, error) { + return []hostsensor.HostSensorDataEnvelope{}, nil, nil } func (hshm *HostSensorHandlerMock) GetNamespace() string { diff --git a/core/pkg/hostsensorutils/utils.go b/core/pkg/hostsensorutils/utils.go new file mode 100644 index 00000000..4f3eeb84 --- /dev/null +++ b/core/pkg/hostsensorutils/utils.go @@ -0,0 +1,35 @@ +package hostsensorutils + +import ( + "github.com/armosec/k8s-interface/k8sinterface" + "github.com/armosec/opa-utils/reporthandling/apis" +) + +var ( + KubeletConfiguration = "KubeletConfiguration" + OsReleaseFile = "OsReleaseFile" + KernelVersion = "KernelVersion" + LinuxSecurityHardeningStatus = "LinuxSecurityHardeningStatus" + OpenPortsList = "OpenPortsList" + LinuxKernelVariables = "LinuxKernelVariables" + KubeletCommandLine = "KubeletCommandLine" + + MapResourceToApiGroup = map[string]string{ + KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0", + OsReleaseFile: "hostdata.kubescape.cloud/v1beta0/", + KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0/", + KernelVersion: "hostdata.kubescape.cloud/v1beta0/", + LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0/", + OpenPortsList: "hostdata.kubescape.cloud/v1beta0/", + LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0/", + } +) + +func addInfoToMap(resource string, infoMap map[string]apis.StatusInfo, err error) { + group, version := k8sinterface.SplitApiVersion(MapResourceToApiGroup[resource]) + r := k8sinterface.JoinResourceTriplets(group, version, resource) + infoMap[r] = apis.StatusInfo{ + InnerStatus: apis.StatusSkipped, + InnerInfo: err.Error(), + } +} diff --git a/core/pkg/opaprocessor/processorhandler.go b/core/pkg/opaprocessor/processorhandler.go index 9223a9e0..4e753a82 100644 --- a/core/pkg/opaprocessor/processorhandler.go +++ b/core/pkg/opaprocessor/processorhandler.go @@ -133,7 +133,7 @@ func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule) (map[stri postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs) // get store - inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.AllResources, rule)) + inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.ArmoResource, opap.AllResources, rule)) if err != nil { return nil, fmt.Errorf("error getting aggregated k8sObjects: %s", err.Error()) } diff --git a/core/pkg/opaprocessor/processorhandlerutils.go b/core/pkg/opaprocessor/processorhandlerutils.go index dba42288..a3aada18 100644 --- a/core/pkg/opaprocessor/processorhandlerutils.go +++ b/core/pkg/opaprocessor/processorhandlerutils.go @@ -9,6 +9,7 @@ import ( "github.com/armosec/k8s-interface/k8sinterface" "github.com/armosec/k8s-interface/workloadinterface" "github.com/armosec/opa-utils/reporthandling" + "github.com/armosec/opa-utils/reporthandling/apis" resources "github.com/armosec/opa-utils/resources" ) @@ -46,8 +47,9 @@ func (opap *OPAProcessor) updateResults() { } // set result summary - opap.Report.SummaryDetails.InitResourcesSummary() - + // map control to error + controlToInfoMap := mapControlToInfo(opap.ResourceToControlsMap, opap.InfoMap) + opap.Report.SummaryDetails.InitResourcesSummary(controlToInfoMap) // for f := range opap.PostureReport.FrameworkReports { // // set exceptions // exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName) @@ -60,13 +62,50 @@ func (opap *OPAProcessor) updateResults() { // } } -func getAllSupportedObjects(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, rule *reporthandling.PolicyRule) []workloadinterface.IMetadata { +func mapControlToInfo(mapResourceToControls map[string][]string, infoMap map[string]apis.StatusInfo) map[string]apis.StatusInfo { + controlToInfoMap := make(map[string]apis.StatusInfo) + for resource, statusInfo := range infoMap { + controls := mapResourceToControls[resource] + for _, control := range controls { + controlToInfoMap[control] = statusInfo + } + } + return controlToInfoMap +} + +func getAllSupportedObjects(k8sResources *cautils.K8SResources, armoResources *cautils.ArmoResources, allResources map[string]workloadinterface.IMetadata, rule *reporthandling.PolicyRule) []workloadinterface.IMetadata { k8sObjects := []workloadinterface.IMetadata{} k8sObjects = append(k8sObjects, getKubernetesObjects(k8sResources, allResources, rule.Match)...) - k8sObjects = append(k8sObjects, getKubernetesObjects(k8sResources, allResources, rule.DynamicMatch)...) + k8sObjects = append(k8sObjects, getArmoObjects(armoResources, allResources, rule.DynamicMatch)...) return k8sObjects } +func getArmoObjects(k8sResources *cautils.ArmoResources, allResources map[string]workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata { + k8sObjects := []workloadinterface.IMetadata{} + + for m := range match { + for _, groups := range match[m].APIGroups { + for _, version := range match[m].APIVersions { + for _, resource := range match[m].Resources { + groupResources := k8sinterface.ResourceGroupToString(groups, version, resource) + for _, groupResource := range groupResources { + if k8sObj, ok := (*k8sResources)[groupResource]; ok { + if k8sObj == nil { + logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource)) + } + for i := range k8sObj { + k8sObjects = append(k8sObjects, allResources[k8sObj[i]]) + } + } + } + } + } + } + } + + return filterOutChildResources(k8sObjects, match) +} + func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata { k8sObjects := []workloadinterface.IMetadata{} diff --git a/core/pkg/policyhandler/handlenotification.go b/core/pkg/policyhandler/handlenotification.go index 91c50485..35fd6434 100644 --- a/core/pkg/policyhandler/handlenotification.go +++ b/core/pkg/policyhandler/handlenotification.go @@ -2,7 +2,9 @@ package policyhandler import ( "fmt" + "strings" + "github.com/armosec/k8s-interface/k8sinterface" "github.com/armosec/kubescape/core/cautils" "github.com/armosec/kubescape/core/pkg/resourcehandler" "github.com/armosec/opa-utils/reporthandling" @@ -45,16 +47,40 @@ func (policyHandler *PolicyHandler) CollectResources(notification *reporthandlin return opaSessionObj, nil } -func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error { +func scanInfoToScanMetadata(opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) { + opaSessionObj.Metadata.ClusterMetadata.ContextName = k8sinterface.GetCurrentContext().Cluster + opaSessionObj.Metadata.ScanMetadata.Format = scanInfo.Format + opaSessionObj.Metadata.ScanMetadata.Submit = scanInfo.Submit + if len(scanInfo.ExcludedNamespaces) > 1 { + opaSessionObj.Metadata.ScanMetadata.ExcludedNamespaces = strings.Split(scanInfo.ExcludedNamespaces[1:], ",") + } + // scan type + if len(scanInfo.PolicyIdentifier) > 0 { + opaSessionObj.Metadata.ScanMetadata.TargetType = string(scanInfo.PolicyIdentifier[0].Kind) + } + // append frameworks + for _, policy := range scanInfo.PolicyIdentifier { + opaSessionObj.Metadata.ScanMetadata.TargetNames = append(opaSessionObj.Metadata.ScanMetadata.TargetNames, policy.Name) + } + opaSessionObj.Metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode + opaSessionObj.Metadata.ScanMetadata.FailThreshold = scanInfo.FailThreshold + opaSessionObj.Metadata.ScanMetadata.HostScanner = *scanInfo.HostSensorEnabled.Get() + opaSessionObj.Metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode + opaSessionObj.Metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs +} +func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error { opaSessionObj.Report.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo() - resourcesMap, allResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj.Policies, ¬ification.Designators) + scanInfoToScanMetadata(opaSessionObj, scanInfo) + + resourcesMap, allResources, armoResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj, ¬ification.Designators) if err != nil { return err } opaSessionObj.K8SResources = resourcesMap opaSessionObj.AllResources = allResources + opaSessionObj.ArmoResource = armoResources return nil } diff --git a/core/pkg/resourcehandler/filesloader.go b/core/pkg/resourcehandler/filesloader.go index e11366e9..e9d6ff59 100644 --- a/core/pkg/resourcehandler/filesloader.go +++ b/core/pkg/resourcehandler/filesloader.go @@ -10,7 +10,6 @@ import ( "github.com/armosec/k8s-interface/k8sinterface" "github.com/armosec/kubescape/core/cautils" - "github.com/armosec/opa-utils/reporthandling" ) // FileResourceHandler handle resources from files and URLs @@ -27,19 +26,20 @@ func NewFileResourceHandler(inputPatterns []string, registryAdaptors *RegistryAd } } -func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error) { +func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASessionObj, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error) { // build resources map // map resources based on framework required resources: map["/group/version/kind"][] - k8sResources := setResourceMap(frameworks) + k8sResources := setK8sResourceMap(sessionObj.Policies) allResources := map[string]workloadinterface.IMetadata{} + var armoResources *cautils.ArmoResources workloads := []workloadinterface.IMetadata{} // load resource from local file system w, err := cautils.LoadResourcesFromFiles(fileHandler.inputPatterns) if err != nil { - return nil, allResources, err + return nil, allResources, nil, err } if w != nil { workloads = append(workloads, w...) @@ -48,14 +48,14 @@ func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling // load resources from url w, err = loadResourcesFromUrl(fileHandler.inputPatterns) if err != nil { - return nil, allResources, err + return nil, allResources, nil, err } if w != nil { workloads = append(workloads, w...) } if len(workloads) == 0 { - return nil, allResources, fmt.Errorf("empty list of workloads - no workloads found") + return nil, allResources, nil, fmt.Errorf("empty list of workloads - no workloads found") } // map all resources: map["/group/version/kind"][] @@ -73,11 +73,11 @@ func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling } } - if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources); err != nil { + if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources, armoResources); err != nil { cautils.WarningDisplay(os.Stderr, "Warning: failed to collect images vulnerabilities: %s\n", err.Error()) } - return k8sResources, allResources, nil + return k8sResources, allResources, armoResources, nil } diff --git a/core/pkg/resourcehandler/k8sresources.go b/core/pkg/resourcehandler/k8sresources.go index a489389a..73dd8207 100644 --- a/core/pkg/resourcehandler/k8sresources.go +++ b/core/pkg/resourcehandler/k8sresources.go @@ -10,7 +10,7 @@ import ( "github.com/armosec/kubescape/core/cautils/logger/helpers" "github.com/armosec/kubescape/core/pkg/hostsensorutils" "github.com/armosec/opa-utils/objectsenvelopes" - "github.com/armosec/opa-utils/reporthandling" + "github.com/armosec/opa-utils/reporthandling/apis" "github.com/armosec/k8s-interface/cloudsupport" "github.com/armosec/k8s-interface/k8sinterface" @@ -44,46 +44,88 @@ func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, fieldSelector IField } } -func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error) { +func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessionObj, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error) { allResources := map[string]workloadinterface.IMetadata{} // get k8s resources logger.L().Info("Accessing Kubernetes objects") cautils.StartSpinner() - + resourceToControl := make(map[string][]string) // build resources map // map resources based on framework required resources: map["/group/version/kind"][] - k8sResourcesMap := setResourceMap(frameworks) + k8sResourcesMap := setK8sResourceMap(sessionObj.Policies) // get namespace and labels from designator (ignore cluster labels) _, namespace, labels := armotypes.DigestPortalDesignator(designator) // pull k8s recourses + armoResourceMap := setArmoResourceMap(sessionObj.Policies, resourceToControl) + + // map of armo resources to control_ids + sessionObj.ResourceToControlsMap = resourceToControl + if err := k8sHandler.pullResources(k8sResourcesMap, allResources, namespace, labels); err != nil { cautils.StopSpinner() - return k8sResourcesMap, allResources, err + return k8sResourcesMap, allResources, armoResourceMap, err } - if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources); err != nil { - logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err)) + numberOfWorkerNodes, err := k8sHandler.pullWorkerNodesNumber() + + if err != nil { + logger.L().Debug("failed to collect worker nodes number", helpers.Error(err)) + } else { + sessionObj.Metadata.ClusterMetadata.NumberOfWorkerNodes = numberOfWorkerNodes } - if err := k8sHandler.collectHostResources(allResources, k8sResourcesMap); err != nil { - logger.L().Warning("failed to collect host scanner resources", helpers.Error(err)) + imgVulnResources := cautils.MapImageVulnResources(armoResourceMap) + // check that controls use image vulnerability resources + if len(imgVulnResources) > 0 { + if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, armoResourceMap); err != nil { + logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err)) + } + } + + hostResources := cautils.MapHostResources(armoResourceMap) + // check that controls use host sensor resources + if len(hostResources) > 0 { + if sessionObj.Metadata.ScanMetadata.HostScanner { + infoMap, err := k8sHandler.collectHostResources(allResources, armoResourceMap) + if err != nil { + logger.L().Warning("failed to collect host scanner resources", helpers.Error(err)) + cautils.SetInfoMapForResources(err.Error(), hostResources, sessionObj.InfoMap) + } else if k8sHandler.hostSensorHandler == nil { + // using hostSensor mock + cautils.SetInfoMapForResources("failed to init host scanner", hostResources, sessionObj.InfoMap) + } else { + sessionObj.InfoMap = infoMap + } + } else { + cautils.SetInfoMapForResources("enable-host-scan flag not used", hostResources, sessionObj.InfoMap) + } } if err := k8sHandler.collectRbacResources(allResources); err != nil { logger.L().Warning("failed to collect rbac resources", helpers.Error(err)) } - if err := getCloudProviderDescription(allResources, k8sResourcesMap); err != nil { - logger.L().Warning("failed to collect cloud data", helpers.Error(err)) + + cloudResources := cautils.MapCloudResources(armoResourceMap) + // check that controls use cloud resources + if len(cloudResources) > 0 { + provider, err := getCloudProviderDescription(allResources, armoResourceMap) + if err != nil { + cautils.SetInfoMapForResources(err.Error(), cloudResources, sessionObj.InfoMap) + logger.L().Warning("failed to collect cloud data", helpers.Error(err)) + } + if provider != "" { + sessionObj.Metadata.ClusterMetadata.CloudProvider = provider + } } cautils.StopSpinner() logger.L().Success("Accessed to Kubernetes objects") - return k8sResourcesMap, allResources, nil + return k8sResourcesMap, allResources, armoResourceMap, nil } func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info { @@ -180,12 +222,11 @@ func ConvertMapListToMeta(resourceMap []map[string]interface{}) []workloadinterf // } // return nil // } -func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[string]workloadinterface.IMetadata, resourcesMap *cautils.K8SResources) error { +func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) (map[string]apis.StatusInfo, error) { logger.L().Debug("Collecting host scanner resources") - - hostResources, err := k8sHandler.hostSensorHandler.CollectResources() + hostResources, infoMap, err := k8sHandler.hostSensorHandler.CollectResources() if err != nil { - return err + return nil, err } for rscIdx := range hostResources { @@ -193,13 +234,13 @@ func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[stri groupResource := k8sinterface.JoinResourceTriplets(group, version, hostResources[rscIdx].GetKind()) allResources[hostResources[rscIdx].GetID()] = &hostResources[rscIdx] - grpResourceList, ok := (*resourcesMap)[groupResource] + grpResourceList, ok := (*armoResourceMap)[groupResource] if !ok { grpResourceList = make([]string, 0) } - (*resourcesMap)[groupResource] = append(grpResourceList, hostResources[rscIdx].GetID()) + (*armoResourceMap)[groupResource] = append(grpResourceList, hostResources[rscIdx].GetID()) } - return nil + return infoMap, nil } func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[string]workloadinterface.IMetadata) error { @@ -218,20 +259,19 @@ func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[stri return nil } -func getCloudProviderDescription(allResources map[string]workloadinterface.IMetadata, k8sResourcesMap *cautils.K8SResources) error { +func getCloudProviderDescription(allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) (string, error) { logger.L().Debug("Collecting cloud data") - cloudProvider := initCloudProvider() cluster := cloudProvider.getKubeCluster() clusterName := cloudProvider.getKubeClusterName() provider := getCloudProvider() region, err := cloudProvider.getRegion(cluster, provider) if err != nil { - return err + return provider, err } project, err := cloudProvider.getProject(cluster, provider) if err != nil { - return err + return provider, err } if provider != "" { @@ -242,17 +282,33 @@ func getCloudProviderDescription(allResources map[string]workloadinterface.IMeta // Return error with useful info on how to configure credentials for getting cloud provider info switch provider { case "gke": - return 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) + 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 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) + 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 fmt.Errorf("could not get descriptive information about aks cluster: %s. %v", cluster, err.Error()) + return provider, fmt.Errorf("could not get descriptive information about aks cluster: %s. %v", cluster, err.Error()) } - return err + return provider, err } allResources[wl.GetID()] = wl - (*k8sResourcesMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()} + (*armoResourceMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()} } - return nil + return provider, nil } + +func (k8sHandler *K8sResourceHandler) pullWorkerNodesNumber() (int, error) { + // labels used for control plane + listOptions := metav1.ListOptions{ + LabelSelector: "!node-role.kubernetes.io/control-plane,!node-role.kubernetes.io/master", + } + nodesList, err := k8sHandler.k8s.KubernetesClient.CoreV1().Nodes().List(context.TODO(), listOptions) + if err != nil { + return 0, err + } + nodesNumber := 0 + if nodesList != nil { + nodesNumber = len(nodesList.Items) + } + return nodesNumber, nil +} diff --git a/core/pkg/resourcehandler/k8sresourcesutils.go b/core/pkg/resourcehandler/k8sresourcesutils.go index 0061c3ea..167a5722 100644 --- a/core/pkg/resourcehandler/k8sresourcesutils.go +++ b/core/pkg/resourcehandler/k8sresourcesutils.go @@ -4,14 +4,23 @@ import ( "strings" "github.com/armosec/kubescape/core/cautils" + "github.com/armosec/kubescape/core/pkg/hostsensorutils" "github.com/armosec/opa-utils/reporthandling" + "k8s.io/utils/strings/slices" "github.com/armosec/k8s-interface/k8sinterface" ) -func setResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources { +var ( + ClusterDescribe = "ClusterDescribe" + + MapResourceToApiGroupCloud = map[string][]string{ + ClusterDescribe: {"container.googleapis.com/v1", "eks.amazonaws.com/v1"}} +) + +func setK8sResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources { k8sResources := make(cautils.K8SResources) - complexMap := setComplexResourceMap(frameworks) + complexMap := setComplexK8sResourceMap(frameworks) for group := range complexMap { for version := range complexMap[group] { for resource := range complexMap[group][version] { @@ -25,33 +34,87 @@ func setResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources return &k8sResources } +func setArmoResourceMap(frameworks []reporthandling.Framework, resourceToControl map[string][]string) *cautils.ArmoResources { + armoResources := make(cautils.ArmoResources) + complexMap := setComplexArmoResourceMap(frameworks, resourceToControl) + for group := range complexMap { + for version := range complexMap[group] { + for resource := range complexMap[group][version] { + groupResources := k8sinterface.ResourceGroupToString(group, version, resource) + for _, groupResource := range groupResources { + armoResources[groupResource] = nil + } + } + } + } + return &armoResources +} + func convertComplexResourceMap(frameworks []reporthandling.Framework) map[string]map[string]map[string]interface{} { k8sResources := make(map[string]map[string]map[string]interface{}) for _, framework := range frameworks { for _, control := range framework.Controls { for _, rule := range control.Rules { for _, match := range rule.Match { - insertK8sResources(k8sResources, match) + insertResources(k8sResources, match) } } } } return k8sResources } -func setComplexResourceMap(frameworks []reporthandling.Framework) map[string]map[string]map[string]interface{} { +func setComplexK8sResourceMap(frameworks []reporthandling.Framework) map[string]map[string]map[string]interface{} { k8sResources := make(map[string]map[string]map[string]interface{}) for _, framework := range frameworks { for _, control := range framework.Controls { for _, rule := range control.Rules { for _, match := range rule.Match { - insertK8sResources(k8sResources, match) + insertResources(k8sResources, match) } } } } return k8sResources } -func insertK8sResources(k8sResources map[string]map[string]map[string]interface{}, match reporthandling.RuleMatchObjects) { + +// [group][versionn][resource] +func setComplexArmoResourceMap(frameworks []reporthandling.Framework, resourceToControls map[string][]string) map[string]map[string]map[string]interface{} { + k8sResources := make(map[string]map[string]map[string]interface{}) + for _, framework := range frameworks { + for _, control := range framework.Controls { + for _, rule := range control.Rules { + for _, match := range rule.DynamicMatch { + insertArmoResourcesAndControls(k8sResources, match, resourceToControls, control) + } + } + } + } + return k8sResources +} + +func mapArmoResourceToApiGroup(resource string) []string { + if val, ok := hostsensorutils.MapResourceToApiGroup[resource]; ok { + return []string{val} + } + return MapResourceToApiGroupCloud[resource] +} + +func insertControls(resource string, resourceToControl map[string][]string, control reporthandling.Control) { + armoResources := mapArmoResourceToApiGroup(resource) + for _, armoResource := range armoResources { + group, version := k8sinterface.SplitApiVersion(armoResource) + r := k8sinterface.JoinResourceTriplets(group, version, resource) + if _, ok := resourceToControl[r]; !ok { + resourceToControl[r] = append(resourceToControl[r], control.ControlID) + } else { + if !slices.Contains(resourceToControl[r], control.ControlID) { + resourceToControl[r] = append(resourceToControl[r], control.ControlID) + } + } + } +} + +func insertResources(k8sResources map[string]map[string]map[string]interface{}, match reporthandling.RuleMatchObjects) { for _, apiGroup := range match.APIGroups { if v, ok := k8sResources[apiGroup]; !ok || v == nil { k8sResources[apiGroup] = make(map[string]map[string]interface{}) @@ -69,6 +132,25 @@ func insertK8sResources(k8sResources map[string]map[string]map[string]interface{ } } +func insertArmoResourcesAndControls(k8sResources map[string]map[string]map[string]interface{}, match reporthandling.RuleMatchObjects, resourceToControl map[string][]string, control reporthandling.Control) { + for _, apiGroup := range match.APIGroups { + if v, ok := k8sResources[apiGroup]; !ok || v == nil { + k8sResources[apiGroup] = make(map[string]map[string]interface{}) + } + for _, apiVersions := range match.APIVersions { + if v, ok := k8sResources[apiGroup][apiVersions]; !ok || v == nil { + k8sResources[apiGroup][apiVersions] = make(map[string]interface{}) + } + for _, resource := range match.Resources { + if _, ok := k8sResources[apiGroup][apiVersions][resource]; !ok { + k8sResources[apiGroup][apiVersions][resource] = nil + } + insertControls(resource, resourceToControl, control) + } + } + } +} + func getGroupNVersion(apiVersion string) (string, string) { gv := strings.Split(apiVersion, "/") group, version := "", "" diff --git a/core/pkg/resourcehandler/k8sresourcesutils_test.go b/core/pkg/resourcehandler/k8sresourcesutils_test.go index ebb0b751..79ea71a3 100644 --- a/core/pkg/resourcehandler/k8sresourcesutils_test.go +++ b/core/pkg/resourcehandler/k8sresourcesutils_test.go @@ -13,7 +13,7 @@ func TestGetK8sResources(t *testing.T) { func TestSetResourceMap(t *testing.T) { k8sinterface.InitializeMapResourcesMock() framework := reporthandling.MockFrameworkA() - k8sResources := setResourceMap([]reporthandling.Framework{*framework}) + k8sResources := setK8sResourceMap([]reporthandling.Framework{*framework}) resources := k8sinterface.ResourceGroupToString("*", "v1", "Pod") if len(resources) == 0 { t.Error("expected resources") @@ -43,9 +43,9 @@ func TestInsertK8sResources(t *testing.T) { APIVersions: []string{"v1"}, Resources: []string{"secrets"}, } - insertK8sResources(k8sResources, match1) - insertK8sResources(k8sResources, match2) - insertK8sResources(k8sResources, match3) + insertResources(k8sResources, match1) + insertResources(k8sResources, match2) + insertResources(k8sResources, match3) apiGroup1, ok := k8sResources["apps"] if !ok { diff --git a/core/pkg/resourcehandler/registrydata.go b/core/pkg/resourcehandler/registrydata.go index 35b3646a..c0a4bee7 100644 --- a/core/pkg/resourcehandler/registrydata.go +++ b/core/pkg/resourcehandler/registrydata.go @@ -34,7 +34,7 @@ func NewRegistryAdaptors() (*RegistryAdaptors, error) { return registryAdaptors, nil } -func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResourcesMap *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata) error { +func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResourcesMap *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) error { logger.L().Debug("Collecting images vulnerabilities") // list cluster images @@ -64,7 +64,7 @@ func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResour for i := range metaObjs { allResources[metaObjs[i].GetID()] = metaObjs[i] } - (*k8sResourcesMap)[k8sinterface.JoinResourceTriplets(ImagevulnerabilitiesObjectGroup, ImagevulnerabilitiesObjectVersion, ImagevulnerabilitiesObjectKind)] = workloadinterface.ListMetaIDs(metaObjs) + (*armoResourceMap)[k8sinterface.JoinResourceTriplets(ImagevulnerabilitiesObjectGroup, ImagevulnerabilitiesObjectVersion, ImagevulnerabilitiesObjectKind)] = workloadinterface.ListMetaIDs(metaObjs) return nil } diff --git a/core/pkg/resourcehandler/resourceshandler.go b/core/pkg/resourcehandler/resourceshandler.go index e8133e90..fa3f439b 100644 --- a/core/pkg/resourcehandler/resourceshandler.go +++ b/core/pkg/resourcehandler/resourceshandler.go @@ -4,11 +4,10 @@ import ( "github.com/armosec/armoapi-go/armotypes" "github.com/armosec/k8s-interface/workloadinterface" "github.com/armosec/kubescape/core/cautils" - "github.com/armosec/opa-utils/reporthandling" "k8s.io/apimachinery/pkg/version" ) type IResourceHandler interface { - GetResources([]reporthandling.Framework, *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error) + GetResources(*cautils.OPASessionObj, *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error) GetClusterAPIServerInfo() *version.Info } diff --git a/core/pkg/resultshandling/printer/v2/controltable.go b/core/pkg/resultshandling/printer/v2/controltable.go index ea26300b..67f9d1c5 100644 --- a/core/pkg/resultshandling/printer/v2/controltable.go +++ b/core/pkg/resultshandling/printer/v2/controltable.go @@ -7,7 +7,7 @@ import ( "github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary" ) -func generateRow(controlSummary reportsummary.IControlSummary) []string { +func generateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfoMap map[string]string) []string { row := []string{controlSummary.GetName()} row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())) row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded())) @@ -15,8 +15,14 @@ func generateRow(controlSummary reportsummary.IControlSummary) []string { if !controlSummary.GetStatus().IsSkipped() { row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%") + row = append(row, "") } else { - row = append(row, "skipped") + row = append(row, string(controlSummary.GetStatus().Status())) + if controlSummary.GetStatus().IsSkipped() { + row = append(row, infoToPrintInfoMap[controlSummary.GetStatus().Info()]) + } else { + row = append(row, "") + } } return row } @@ -32,5 +38,5 @@ func getSortedControlsNames(controls reportsummary.ControlSummaries) []string { } func getControlTableHeaders() []string { - return []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE"} + return []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE", "INFO"} } diff --git a/core/pkg/resultshandling/printer/v2/pdf.go b/core/pkg/resultshandling/printer/v2/pdf.go index 9a7d8a27..32090030 100644 --- a/core/pkg/resultshandling/printer/v2/pdf.go +++ b/core/pkg/resultshandling/printer/v2/pdf.go @@ -54,15 +54,37 @@ func (pdfPrinter *PdfPrinter) SetWriter(outputFile string) { func (pdfPrinter *PdfPrinter) Score(score float32) { fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score)) } +func (pdfPrinter *PdfPrinter) printInfo(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, infoMap map[string]string) { + emptyRowCounter := 1 + for key, val := range infoMap { + if val != "" { + m.Row(5, func() { + m.Col(1, func() { + m.Text(fmt.Sprintf("%v", val)) + }) + m.Col(12, func() { + m.Text(fmt.Sprintf("%v", key)) + }) + }) + if emptyRowCounter < len(infoMap) { + m.Row(2.5, func() {}) + emptyRowCounter++ + } + } + } + +} func (pdfPrinter *PdfPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { pdfPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) + infoToPrintInfoMap := 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.printFinalResult(m, &opaSessionObj.Report.SummaryDetails) + pdfPrinter.printInfo(m, &opaSessionObj.Report.SummaryDetails, infoToPrintInfoMap) // Extrat output buffer. outBuff, err := m.Output() @@ -129,12 +151,13 @@ func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsu // Create pdf table func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) { headers := getControlTableHeaders() + infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls) controls := make([][]string, len(pdfPrinter.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])) + controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, pdfPrinter.sortedControlNames[i]), infoToPrintInfoMap) } m.TableList(headers, controls, props.TableList{ diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter.go b/core/pkg/resultshandling/printer/v2/prettyprinter.go index 30ad8626..4cc3daef 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter.go @@ -83,10 +83,17 @@ func (prettyPrinter *PrettyPrinter) printTitle(controlSummary reportsummary.ICon cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace) case apis.StatusExcluded: cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace) + case apis.StatusIrrelevant: + cautils.SuccessDisplay(prettyPrinter.writer, "irrelevant %v\n", emoji.ConfusedFace) + case apis.StatusError: + cautils.WarningDisplay(prettyPrinter.writer, "error %v\n", emoji.ConfusedFace) default: cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp) } cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.GetDescription()) + if controlSummary.GetStatus().Info() != "" { + cautils.WarningDisplay(prettyPrinter.writer, "Reason: %v\n", controlSummary.GetStatus().Info()) + } } func (prettyPrinter *PrettyPrinter) printResources(controlSummary reportsummary.IControlSummary, allResources map[string]workloadinterface.IMetadata) { @@ -166,6 +173,7 @@ func generateFooter(summaryDetails *reportsummary.SummaryDetails) []string { 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, " ") return row } @@ -175,30 +183,37 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm 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} + alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER} summaryTable.SetColumnAlignment(alignments) - + infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls) for i := 0; i < len(prettyPrinter.sortedControlNames); i++ { - summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]))) + summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]), infoToPrintInfoMap)) } summaryTable.SetFooter(generateFooter(summaryDetails)) // summaryTable.SetFooter(generateFooter()) + cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks())) summaryTable.Render() + prettyPrinter.printInfo(infoToPrintInfoMap) // For control scan framework will be nil - cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks())) +} + +func (prettyPrinter *PrettyPrinter) printInfo(infoToPrintInfoMap map[string]string) { + for info, stars := range infoToPrintInfoMap { + cautils.WarningDisplay(prettyPrinter.writer, fmt.Sprintf("%s - %s\n", stars, info)) + } } func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string { if len(frameworks) == 1 { if frameworks[0].GetName() != "" { - return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName()) + return fmt.Sprintf("\nFRAMEWORK %s\n", frameworks[0].GetName()) // cautils.InfoTextDisplay(prettyPrinter.writer, )) } } else if len(frameworks) > 1 { - p := "FRAMEWORKS: " + p := "\nFRAMEWORKS: " i := 0 for ; i < len(frameworks)-1; i++ { p += fmt.Sprintf("%s (risk: %.2f), ", frameworks[i].GetName(), frameworks[i].GetScore()) diff --git a/core/pkg/resultshandling/printer/v2/utils.go b/core/pkg/resultshandling/printer/v2/utils.go index 08d899d5..45e107e6 100644 --- a/core/pkg/resultshandling/printer/v2/utils.go +++ b/core/pkg/resultshandling/printer/v2/utils.go @@ -6,6 +6,7 @@ import ( "github.com/armosec/kubescape/core/cautils/logger" "github.com/armosec/kubescape/core/cautils/logger/helpers" "github.com/armosec/opa-utils/reporthandling" + "github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary" "github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults" reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2" ) @@ -38,6 +39,20 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri } } +func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) map[string]string { + infoToPrintInfoMap := make(map[string]string) + starCount := "*" + for _, control := range controls { + if control.GetStatus().IsSkipped() && control.GetStatus().Info() != "" { + if _, ok := infoToPrintInfoMap[control.GetStatus().Info()]; !ok { + infoToPrintInfoMap[control.GetStatus().Info()] = starCount + starCount += starCount + } + } + } + return infoToPrintInfoMap +} + func finalizeResources(resources []reporthandling.Resource, results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) { for i := range results { if obj, ok := allResources[results[i].ResourceID]; ok { diff --git a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go index d57290e2..fb15248a 100644 --- a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go +++ b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go @@ -60,6 +60,7 @@ func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASe opaSessionObj.Report.ReportID = uuid.NewString() opaSessionObj.Report.CustomerGUID = report.customerGUID opaSessionObj.Report.ClusterName = report.clusterName + opaSessionObj.Report.Metadata = *opaSessionObj.Metadata if err := report.prepareReport(opaSessionObj.Report); err != nil { logger.L().Error("failed to publish results", helpers.Error(err)) diff --git a/docs/ARMO-header-2022.gif b/docs/ARMO-header-2022.gif new file mode 100644 index 00000000..09d4adab Binary files /dev/null and b/docs/ARMO-header-2022.gif differ diff --git a/httphandler/go.mod b/httphandler/go.mod index 5c09fbdb..e4e173dd 100644 --- a/httphandler/go.mod +++ b/httphandler/go.mod @@ -4,6 +4,7 @@ go 1.17 replace github.com/armosec/kubescape/core => ../core + require ( github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000 github.com/armosec/utils-go v0.0.3 diff --git a/httphandler/go.sum b/httphandler/go.sum index 5193f2eb..c6e3f228 100644 --- a/httphandler/go.sum +++ b/httphandler/go.sum @@ -103,15 +103,10 @@ github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qm github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw= github.com/armosec/armoapi-go v0.0.58 h1:gLVLTG1QRphz8vbkZdG8syijwNSh3oBVwNmn91us4gc= github.com/armosec/armoapi-go v0.0.58/go.mod h1:U/Axd+D5N00x9Ekr7t+5HXqLCMO+98NfJSVAggqJftI= -github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM= github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU= github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W2muqX9AxKhkb0Q= 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.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0= -github.com/armosec/opa-utils v0.0.118/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= github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=