Compare commits

..

43 Commits

Author SHA1 Message Date
dwertent
b616a37800 fixed test 2022-01-05 16:45:50 +02:00
dwertent
ce488a3645 update latest fixes 2022-01-05 16:45:02 +02:00
dwertent
fb47a9c742 support v2 printing 2022-01-05 16:38:37 +02:00
David Wertenteil
80ace81a12 Fixing typo in the ActionSendReport error message 2022-01-05 16:16:52 +02:00
Jonas Kint
bbf68d4ce8 Fixing typo in the ActionSendReport error message 2022-01-05 13:49:26 +01:00
dwertent
e1eec47a22 fixed report 2022-01-04 18:28:48 +02:00
Rotem Refael
fc05075817 Merge pull request #294 from armosec/dev
Minor features and improvements
2022-01-04 15:29:55 +02:00
dwertent
5bb64b634a support loading ks config in env 2022-01-04 14:42:25 +02:00
dwertent
7bc2c2be13 fliter ot reources based on owners 2022-01-03 13:36:29 +02:00
dwertent
1213e8d6ac convert reports 2022-01-02 21:46:09 +02:00
dwertent
3f58d68d2a mocks 2021-12-30 17:48:42 +02:00
Rotem Refael
803e62020e add devopsbest framework 2021-12-30 16:40:07 +02:00
dwertent
fde437312f report v1 to v2 2021-12-30 11:52:17 +02:00
Ben Hirschberg
18425c915b Merge pull request #291 from slashben/dev
adding container image vulnerability adaptor proposal
2021-12-30 10:44:57 +02:00
Benyamin Hirschberg
0de6892ddd adding container image vunerability adaptor proposal 2021-12-30 10:44:08 +02:00
David Wertenteil
dfb92ffec3 Remove RBAC deprecated objects 2021-12-29 17:49:52 +02:00
yiscah
85317f1ee1 Merge branch 'dev' of https://github.com/YiscahLevySilas1/kubescape into dev 2021-12-29 16:23:29 +02:00
yiscah
f22f60508f rbacTable and rbac struct deprecated 2021-12-29 16:23:14 +02:00
dwertent
716bdaaf38 support kind List 2021-12-29 12:06:48 +02:00
dwertent
1b0e2b87de Handle all resources failure 2021-12-28 10:47:12 +02:00
David Wertenteil
2c57b809d2 show warnings for host sensor and send kubelet cmd 2021-12-28 10:42:26 +02:00
David Wertenteil
d9c96db212 Merge branch 'dev' into master 2021-12-28 10:41:39 +02:00
Daniel-GrunbergerCA
5f7391a76b stdout to stderror 2021-12-28 09:20:05 +02:00
Daniel-GrunbergerCA
accd80eda8 rm cmdline map 2021-12-28 09:07:50 +02:00
Daniel-GrunbergerCA
e49499f085 use regoes from master 2021-12-27 08:45:50 +02:00
David Delarosa
521f8930d7 Merge branch 'dev' into dev 2021-12-26 14:43:06 +02:00
David Wertenteil
11b9a8eb6e fix ControlsInputsGetter init 2021-12-23 17:39:49 +02:00
yiscah
0d4350ae24 fix ControlsInputsGetter init 2021-12-23 17:31:23 +02:00
David Wertenteil
62a6a25aa1 support pulling config inputs from git 2021-12-23 16:48:29 +02:00
yiscah
14a74e7312 support pulling config inputs from git 2021-12-23 10:33:23 +02:00
Rotem Refael
3fad2f3430 Merge pull request #279 from armosec/dev
Cli improvements
2021-12-22 21:16:54 +02:00
David Delarosa
c35d1e8791 Use stderr
By using stderr fd we can separate the information logs from the
application output
2021-12-22 20:27:38 +02:00
David Wertenteil
0367255a2a cli improvement
* Support risk-score calculation
* Update spinner support
* Update url display
* Adding control url to each control
2021-12-22 16:56:11 +02:00
Daniel-GrunbergerCA
ad94ac7595 rm json print 2021-12-22 08:29:35 +02:00
Daniel-GrunbergerCA
cfa3993b79 print json 2021-12-21 20:31:12 +02:00
Daniel-GrunbergerCA
972793b98a print json 2021-12-21 20:27:23 +02:00
Daniel-GrunbergerCA
35682bf5b8 pull regoes from dev 2021-12-21 19:02:16 +02:00
Daniel-GrunbergerCA
b023f592aa Merge remote-tracking branch 'upstream/dev' 2021-12-21 13:37:32 +02:00
Daniel-GrunbergerCA
a1c34646f1 waning for host sensor 2021-12-21 13:34:31 +02:00
David Wertenteil
9ac3768f1d Merge pull request #277 from dwertent/master
Fixed host sensor ignore ns
2021-12-21 13:23:20 +02:00
Daniel-GrunbergerCA
0cac7cb1a5 fix kubeletcmd for marshalling 2021-12-21 09:23:38 +02:00
David Wertenteil
8d41d11ca3 Merge pull request #275 from dwertent/master
CLI improvements
2021-12-20 18:26:55 +02:00
David Wertenteil
8543afccca Update objects groping and kinds 2021-12-19 23:34:41 +02:00
56 changed files with 1823 additions and 364 deletions

View File

@@ -19,7 +19,8 @@ Please note we have a code of conduct, please follow it in all your interactions
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. We will merge the Pull Request in once you have the sign-off.
3. Open Pull Request to `dev` branch - we test the component before merging into the `master` branch
4. We will merge the Pull Request in once you have the sign-off.
## Code of Conduct

View File

@@ -14,10 +14,7 @@ import (
corev1 "k8s.io/api/core/v1"
)
const (
configMapName = "kubescape"
configFileName = "config"
)
const configFileName = "config"
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
@@ -145,26 +142,38 @@ func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) err
// ========================== Cluster Config ============================================
// ======================================================================================
// ClusterConfig configuration of specific cluster
/*
Supported environments variables:
KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescape'
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
TODO - supprot:
KS_ACCOUNT // Account ID
KS_CACHE // path to cached files
*/
type ClusterConfig struct {
k8s *k8sinterface.KubernetesApi
defaultNS string
backendAPI getter.IBackend
configObj *ConfigObj
k8s *k8sinterface.KubernetesApi
configMapName string
configMapNamespace string
backendAPI getter.IBackend
configObj *ConfigObj
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, customerGUID string) *ClusterConfig {
defaultNS := k8sinterface.GetDefaultNamespace()
var configObj *ConfigObj
c := &ClusterConfig{
k8s: k8s,
backendAPI: backendAPI,
configObj: &ConfigObj{},
defaultNS: defaultNS,
k8s: k8s,
backendAPI: backendAPI,
configObj: &ConfigObj{},
configMapName: getConfigMapName(),
configMapNamespace: getConfigMapNamespace(),
}
// get from configMap
if existsConfigMap(k8s, defaultNS) {
configObj, _ = loadConfigFromConfigMap(k8s, defaultNS)
if c.existsConfigMap() {
configObj, _ = c.loadConfigFromConfigMap()
} else if existsConfigFile() { // get from file
configObj, _ = loadConfigFromFile()
}
@@ -189,10 +198,10 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
}
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetDefaultNS() string { return c.defaultNS }
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
func (c *ClusterConfig) GetCustomerGUID() string { return c.configObj.CustomerGUID }
func (c *ClusterConfig) IsConfigFound() bool {
return existsConfigFile() || existsConfigMap(c.k8s, c.defaultNS)
return existsConfigFile() || c.existsConfigMap()
}
func (c *ClusterConfig) SetTenant() error {
@@ -202,7 +211,7 @@ func (c *ClusterConfig) SetTenant() error {
return err
}
// update/create config
if existsConfigMap(c.k8s, c.defaultNS) {
if c.existsConfigMap() {
c.updateConfigMap()
} else {
c.createConfigMap()
@@ -223,8 +232,8 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
}
return m
}
func loadConfigFromConfigMap(k8s *k8sinterface.KubernetesApi, ns string) (*ConfigObj, error) {
configMap, err := k8s.KubernetesClient.CoreV1().ConfigMaps(ns).Get(context.Background(), configMapName, metav1.GetOptions{})
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
@@ -235,15 +244,15 @@ func loadConfigFromConfigMap(k8s *k8sinterface.KubernetesApi, ns string) (*Confi
return nil, nil
}
func existsConfigMap(k8s *k8sinterface.KubernetesApi, ns string) bool {
_, err := k8s.KubernetesClient.CoreV1().ConfigMaps(ns).Get(context.Background(), configMapName, metav1.GetOptions{})
func (c *ClusterConfig) existsConfigMap() bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
// TODO - check if has customerGUID
return err == nil
}
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return "", err
@@ -295,11 +304,11 @@ func SetKeyValueInConfigJson(key string, value string) error {
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
configMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Name: c.configMapName,
},
}
}
@@ -311,9 +320,9 @@ func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
configMap.Data[key] = value
if err != nil {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
} else {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
}
return err
@@ -330,12 +339,12 @@ func (c *ClusterConfig) createConfigMap() error {
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Name: c.configMapName,
},
}
c.updateConfigData(configMap)
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
return err
}
@@ -343,7 +352,7 @@ func (c *ClusterConfig) updateConfigMap() error {
if c.k8s == nil {
return nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return err
@@ -351,7 +360,7 @@ func (c *ClusterConfig) updateConfigMap() error {
c.updateConfigData(configMap)
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
return err
}
@@ -394,7 +403,7 @@ func readConfig(dat []byte) (*ConfigObj, error) {
// Check if the customer is submitted
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
return existsConfigMap(clusterConfig.k8s, clusterConfig.defaultNS) || existsConfigFile()
return clusterConfig.existsConfigMap() || existsConfigFile()
}
// Check if the customer is registered
@@ -411,7 +420,7 @@ func (clusterConfig *ClusterConfig) IsRegistered() bool {
}
func (clusterConfig *ClusterConfig) DeleteConfig() error {
if err := DeleteConfigMap(clusterConfig.k8s); err != nil {
if err := clusterConfig.DeleteConfigMap(); err != nil {
return err
}
if err := DeleteConfigFile(); err != nil {
@@ -419,8 +428,8 @@ func (clusterConfig *ClusterConfig) DeleteConfig() error {
}
return nil
}
func DeleteConfigMap(k8s *k8sinterface.KubernetesApi) error {
return k8s.KubernetesClient.CoreV1().ConfigMaps(k8sinterface.GetDefaultNamespace()).Delete(context.Background(), configMapName, metav1.DeleteOptions{})
func (clusterConfig *ClusterConfig) DeleteConfigMap() error {
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
}
func DeleteConfigFile() error {
@@ -430,3 +439,17 @@ func DeleteConfigFile() error {
func AdoptClusterName(clusterName string) string {
return strings.ReplaceAll(clusterName, "/", "-")
}
func getConfigMapName() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
return n
}
return "kubescape"
}
func getConfigMapNamespace() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
return n
}
return "default"
}

View File

@@ -4,25 +4,31 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
type K8SResources map[string][]string
type OPASessionObj struct {
K8SResources *K8SResources // input k8s objects
Frameworks []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
PostureReport *reporthandling.PostureReport // scan results
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
K8SResources *K8SResources // input k8s objects
Frameworks []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
PostureReport *reporthandling.PostureReport // scan results v1
Report *reporthandlingv2.PostureReport // scan results v2
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
}
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
return &OPASessionObj{
Frameworks: frameworks,
K8SResources: k8sResources,
AllResources: make(map[string]workloadinterface.IMetadata),
Report: &reporthandlingv2.PostureReport{},
Frameworks: frameworks,
K8SResources: k8sResources,
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),
PostureReport: &reporthandling.PostureReport{
ClusterName: ClusterName,
CustomerGUID: CustomerGUID,
@@ -32,9 +38,11 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
func NewOPASessionObjMock() *OPASessionObj {
return &OPASessionObj{
Frameworks: nil,
K8SResources: nil,
AllResources: make(map[string]workloadinterface.IMetadata),
Frameworks: nil,
K8SResources: nil,
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),
Report: &reporthandlingv2.PostureReport{},
PostureReport: &reporthandling.PostureReport{
ClusterName: "",
CustomerGUID: "",
@@ -60,3 +68,8 @@ type RegoInputData struct {
// ClusterName string `json:"clusterName"`
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
}
type Policies struct {
Frameworks []string
Controls map[string]reporthandling.Control // map[<control ID>]<control>
}

View File

@@ -1,26 +1,65 @@
package cautils
import (
"encoding/json"
pkgcautils "github.com/armosec/utils-go/utils"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/open-policy-agent/opa/util"
"github.com/armosec/opa-utils/reporthandling"
)
func (data *RegoInputData) SetControlsInputs(controlsInputs map[string][]string) {
data.PostureControlInputs = controlsInputs
func NewPolicies() *Policies {
return &Policies{
Frameworks: make([]string, 0),
Controls: make(map[string]reporthandling.Control),
}
}
func (data *RegoInputData) TOStorage() (storage.Store, error) {
var jsonObj map[string]interface{}
bytesData, err := json.Marshal(*data)
if err != nil {
return nil, err
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string) {
for i := range frameworks {
if frameworks[i].Name != "" {
policies.Frameworks = append(policies.Frameworks, frameworks[i].Name)
}
for j := range frameworks[i].Controls {
compatibleRules := []reporthandling.PolicyRule{}
for r := range frameworks[i].Controls[j].Rules {
if !ruleWithArmoOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) {
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
}
}
frameworks[i].Controls[j].Rules = compatibleRules
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
}
}
// glog.Infof("RegoDependenciesData: %s", bytesData)
if err := util.UnmarshalJSON(bytesData, &jsonObj); err != nil {
return nil, err
}
return inmem.NewFromObject(jsonObj), nil
}
func ruleWithArmoOpaDependency(attributes map[string]interface{}) bool {
if attributes == nil {
return false
}
if s, ok := attributes["armoOpa"]; ok { // TODO - make global
return pkgcautils.StringToBool(s.(string))
}
return false
}
// Checks that kubescape version is in range of use for this rule
// In local build (BuildNumber = ""):
// returns true only if rule doesn't have the "until" attribute
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
if version != "" {
if from.(string) > BuildNumber {
return false
}
}
}
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
if version != "" {
if until.(string) <= BuildNumber {
return false
}
} else {
return false
}
}
return true
}

View File

@@ -35,15 +35,15 @@ func ScanStartDisplay() {
if IsSilent() {
return
}
InfoDisplay(os.Stdout, "ARMO security scanner starting\n")
InfoDisplay(os.Stderr, "ARMO security scanner starting\n")
}
func SuccessTextDisplay(str string) {
if IsSilent() {
return
}
SuccessDisplay(os.Stdout, "[success] ")
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
SuccessDisplay(os.Stderr, "[success] ")
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
}
@@ -60,8 +60,8 @@ func ProgressTextDisplay(str string) {
if IsSilent() {
return
}
InfoDisplay(os.Stdout, "[progress] ")
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
InfoDisplay(os.Stderr, "[progress] ")
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
}
func StartSpinner() {

View File

@@ -100,7 +100,7 @@ func (armoAPI *ArmoAPI) GetReportReceiverURL() string {
func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name), nil)
if err != nil {
return nil, err
return nil, nil
}
framework := &reporthandling.Framework{}

View File

@@ -5,7 +5,7 @@ import (
"strings"
)
var NativeFrameworks = []string{"nsa", "mitre", "armobest"}
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}

View File

@@ -41,7 +41,19 @@ func (drp *DownloadReleasedPolicy) GetFramework(name string) (*reporthandling.Fr
return framework, err
}
func (drp *DownloadReleasedPolicy) GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error) {
defaultConfigInputs, err := drp.gs.GetDefaultConfigInputs()
if err != nil {
return nil, err
}
return defaultConfigInputs.Settings.PostureControlInputs, err
}
func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
if len(fwNames) != 0 && err == nil {
return nil
}
return drp.gs.SetRegoObjects()
}

View File

@@ -0,0 +1,13 @@
package getter
import (
"path/filepath"
)
var mockFrameworkBasePath = filepath.Join("examples", "mocks", "frameworks")
func MockNewLoadPolicy() *LoadPolicy {
return &LoadPolicy{
filePaths: []string{""},
}
}

View File

@@ -43,16 +43,6 @@ func (rbacObjects *RBACObjects) ListAllResources() (map[string]workloadinterface
func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.RbacObjects) (map[string]workloadinterface.IMetadata, error) {
allresources := map[string]workloadinterface.IMetadata{}
// wrap rbac aggregated objects in IMetadata and add to allresources
rbacIMeta, err := rbacutils.RbacObjectIMetadataWrapper(resources.Rbac)
if err != nil {
return nil, err
}
allresources[rbacIMeta.GetID()] = rbacIMeta
rbacTableIMeta, err := rbacutils.RbacTableObjectIMetadataWrapper(resources.RbacT)
if err != nil {
return nil, err
}
allresources[rbacTableIMeta.GetID()] = rbacTableIMeta
SA2WLIDmapIMeta, err := rbacutils.SA2WLIDmapIMetadataWrapper(resources.SA2WLIDmap)
if err != nil {
return nil, err

View File

@@ -77,7 +77,6 @@ type Getters struct {
func (scanInfo *ScanInfo) Init() {
scanInfo.setUseFrom()
scanInfo.setUseExceptions()
scanInfo.setAccountConfig()
scanInfo.setOutputFile()
}
@@ -91,14 +90,6 @@ func (scanInfo *ScanInfo) setUseExceptions() {
}
}
func (scanInfo *ScanInfo) setAccountConfig() {
if scanInfo.ControlsInputs != "" {
// load account config from file
scanInfo.ControlsInputsGetter = getter.NewLoadPolicy([]string{scanInfo.ControlsInputs})
} else {
scanInfo.ControlsInputsGetter = getter.GetArmoAPIConnector()
}
}
func (scanInfo *ScanInfo) setUseFrom() {
if scanInfo.UseDefault {
for _, policy := range scanInfo.PolicyIdentifier {

View File

@@ -22,7 +22,7 @@ type IVersionCheckHandler interface {
func NewIVersionCheckHandler() IVersionCheckHandler {
if BuildNumber == "" {
WarningDisplay(os.Stdout, "Warning: unknown build number, this might affect your scan results. Please make sure you are updated to latest version.\n")
WarningDisplay(os.Stderr, "Warning: unknown build number, this might affect your scan results. Please make sure you are updated to latest version.\n")
}
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) {
return NewVersionCheckHandlerMock()

View File

@@ -8,7 +8,7 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
@@ -32,7 +32,7 @@ var rabcCmd = &cobra.Command{
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetCustomerGUID(), clusterConfig.GetClusterName()))
// submit resources
r := reporter.NewReportEventReceiver(clusterConfig.GetConfigObj())
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,

View File

@@ -10,7 +10,7 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/opa-utils/reporthandling"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
@@ -70,7 +70,7 @@ var resultsCmd = &cobra.Command{
resultsObjects := NewResultsObject(clusterConfig.GetCustomerGUID(), clusterConfig.GetClusterName(), args[0])
// submit resources
r := reporter.NewReportEventReceiver(clusterConfig.GetConfigObj())
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,

View File

@@ -5,15 +5,20 @@ import (
"io/fs"
"os"
"github.com/armosec/kubescape/resultshandling/printer"
printerv1 "github.com/armosec/kubescape/resultshandling/printer/v1"
// printerv2 "github.com/armosec/kubescape/resultshandling/printer/v2"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/hostsensorutils"
"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"
"github.com/armosec/opa-utils/reporthandling"
"github.com/mattn/go-isatty"
@@ -59,7 +64,8 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
// setup printer
printerHandler := printer.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
printerHandler := printerv1.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
// printerHandler = printerv2.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
printerHandler.SetWriter(scanInfo.Output)
return componentInterfaces{
@@ -85,8 +91,10 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
interfaces.report.SetClusterName(interfaces.tenantConfig.GetClusterName())
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetCustomerGUID())
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// set policy getter only after setting the customerGUID
setPolicyGetter(scanInfo, interfaces.tenantConfig.GetCustomerGUID())
setPolicyGetter(scanInfo, interfaces.tenantConfig.GetCustomerGUID(), downloadReleasedPolicy)
setConfigInputsGetter(scanInfo, interfaces.tenantConfig.GetCustomerGUID(), downloadReleasedPolicy)
defer func() {
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
@@ -121,7 +129,7 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
// print report url
interfaces.report.DisplayReportURL()
if score >= float32(scanInfo.FailThreshold) {
if score > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %d", score, scanInfo.FailThreshold)
}

View File

@@ -10,9 +10,11 @@ import (
"github.com/armosec/kubescape/hostsensorutils"
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/kubescape/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/golang/glog"
// reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
)
func getKubernetesApi(scanInfo *cautils.ScanInfo) *k8sinterface.KubernetesApi {
@@ -37,9 +39,9 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
func getReporter(tenantConfig cautils.ITenantConfig, submit bool) reporter.IReport {
if submit {
return reporter.NewReportEventReceiver(tenantConfig.GetConfigObj())
return reporterv1.NewReportEventReceiver(tenantConfig.GetConfigObj())
}
return reporter.NewReportMock()
return reporterv1.NewReportMock()
}
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor) resourcehandler.IResourceHandler {
if scanInfo.GetScanningEnvironment() == cautils.ScanLocalFiles {
@@ -57,11 +59,12 @@ func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.Kubernet
// we need to determined which controls needs host sensor
if scanInfo.HostSensor.Get() == nil && hasHostSensorControls {
scanInfo.HostSensor.SetBool(askUserForHostSensor())
cautils.WarningDisplay(os.Stderr, "Warning: Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag\n")
}
if hostSensorVal := scanInfo.HostSensor.Get(); hostSensorVal != nil && *hostSensorVal {
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s)
if err != nil || hostSensorHandler == nil {
glog.Errorf("failed to create host sensor: %v", err)
if err != nil {
cautils.WarningDisplay(os.Stderr, fmt.Sprintf("Warning: failed to create host sensor: %v\n", err.Error()))
return &hostsensorutils.HostSensorHandlerMock{}
}
return hostSensorHandler
@@ -136,25 +139,40 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
}
// setPolicyGetter set the policy getter - local file/github release/ArmoAPI
func setPolicyGetter(scanInfo *cautils.ScanInfo, customerGUID string) {
func setPolicyGetter(scanInfo *cautils.ScanInfo, customerGUID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) {
if len(scanInfo.UseFrom) > 0 {
scanInfo.PolicyGetter = getter.NewLoadPolicy(scanInfo.UseFrom)
} else {
if customerGUID == "" || !scanInfo.FrameworkScan {
setDownloadReleasedPolicy(scanInfo)
setDownloadReleasedPolicy(scanInfo, downloadReleasedPolicy)
} else {
setGetArmoAPIConnector(scanInfo, customerGUID)
}
}
}
func setDownloadReleasedPolicy(scanInfo *cautils.ScanInfo) {
g := getter.NewDownloadReleasedPolicy() // download policy from github release
if err := g.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
cautils.WarningDisplay(os.Stdout, "Warning: failed to get policies from github release, loading policies from cache\n")
// setConfigInputsGetter sets the config input getter - local file/github release/ArmoAPI
func setConfigInputsGetter(scanInfo *cautils.ScanInfo, customerGUID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) {
if len(scanInfo.ControlsInputs) > 0 {
scanInfo.Getters.ControlsInputsGetter = getter.NewLoadPolicy([]string{scanInfo.ControlsInputs})
} else {
if customerGUID != "" {
scanInfo.Getters.ControlsInputsGetter = getter.GetArmoAPIConnector()
} else {
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull config inputs, fallback to BE
cautils.WarningDisplay(os.Stderr, "Warning: failed to get config inputs from github release, this may affect the scanning results\n")
}
scanInfo.Getters.ControlsInputsGetter = downloadReleasedPolicy
}
}
}
func setDownloadReleasedPolicy(scanInfo *cautils.ScanInfo, downloadReleasedPolicy *getter.DownloadReleasedPolicy) {
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
cautils.WarningDisplay(os.Stderr, "Warning: failed to get policies from github release, loading policies from cache\n")
scanInfo.PolicyGetter = getter.NewLoadPolicy(getDefaultFrameworksPaths())
} else {
scanInfo.PolicyGetter = g
scanInfo.PolicyGetter = downloadReleasedPolicy
}
}
func setGetArmoAPIConnector(scanInfo *cautils.ScanInfo, customerGUID string) {

View File

@@ -0,0 +1,117 @@
# Container image vulnerabilty adaptor interface proposal
## Rationale
source #287
### Big picture
* Kubescape team planning to create controls which take into account image vulnerabilities, example: looking for public internet facing workloads with critical vulnerabilities. These are seriously effecting the security health of a cluster and therefore we think it is important to cover it. We think that most container registries are/will support image scanning like Harbor and therefore the ability to get information from them is important.
* There are information in the image repository which is important for existing controls as well. They are incomplete without it, example see this issue: Non-root containers check is broken #19 . These are not necessarily image vulnerability related. Can be information in the image manifest (like the issue before), but it can be the image BOM related.
### Relation to this proposal
There are multiple changes and design decisions needs to be made before Kubescape will support the before outlined controls. However, a focal point the whole picutre is the ability to access vulnerabilty databases of container images. We anticiapte that most container image repositories will support image vulnerabilty scanning, some major players are already do. Since there is no a single API available which all of these data sources support it is important to create an adaption layer within Kubescape so different datasources can serve Kubescape's goals.
## High level design of Kubescape
### Layers
* Controls and Rules: that actual control logic implementation, the "tests" themselves. Implemented in rego
* OPA engine: the [OPA](https://github.com/open-policy-agent/opa) rego interpreter
* Rules processor: Kubescape component, it enumerates and runs the controls while also preparing the all the input data that the controls need for running
* Data sources: set of different modules providing data to the Rules processor so it can run the controls with them. Examples: Kubernetes objects, cloud vendor API objects and adding in this proposal the vulnerability infomration
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerabilty vendors to adapt to.
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption
```
-----------------------
| Controls/Rules (rego) |
-----------------------
|
-----------------------
| OPA engine |
-----------------------
|
-----------------------
| Rules processor |
-----------------------
|
-----------------------
| Data sources |
-----------------------
|
=======================
| CIV adaption interface| <- Adding this layer in this proposal
=======================
|
-----------------------
| Specific CIV adaptors | <- will be implemented based on this proposal
-----------------------
```
## Functionalities to cover
The interface needs to cover the following functionalities:
* Authentication against the information source (abstracted login)
* Triggering image scan (if applicable, the source might store vulnerabilities for images but cannot scan alone)
* Reading image scan status (with last scan date and etc.)
* Getting vulnerability information for a given image
* Getting image information
* Image manifests
* Image BOMs (bill of material)
## Go API proposal
```
/*type ContainerImageRegistryCredentials struct {
map[string]string
Password string
Tag string
Hash string
}*/
type ContainerImageIdentifier struct {
Registry string
Repository string
Tag string
Hash string
}
type ContainerImageScanStatus struct {
ImageID ContainerImageIdentifier
IsScanAvailable bool
IsBomAvailable bool
LastScanDate time.Time
}
type ContainerImageVulnerability struct {
ImageID ContainerImageIdentifier
// TBD
}
type ContainerImageInformation struct {
ImageID ContainerImageIdentifier
Bom []string
ImageManifest Manifest // will use here Docker package definition
}
type IContainerImageVulnerabilityAdaptor interface {
// Credentials are coming from user input (CLI or configuration file) and they are abstracted at string to string map level
// so and example use would be like registry: "simpledockerregistry:80" and credentials like {"username":"joedoe","password":"abcd1234"}
Login(registry string, credentials map[string]string) error
// For "help" purposes
DescribeAdaptor() string
GetImagesScanStatus(imageIDs []ContainerImageIdentifier) ([]ContainerImageScanStatus, error)
GetImagesVulnerabilties(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerability, error)
GetImagesInformation(imageIDs []ContainerImageIdentifier) ([]ContainerImageInformation, error)
}
```

6
go.mod
View File

@@ -3,10 +3,10 @@ module github.com/armosec/kubescape
go 1.17
require (
github.com/armosec/armoapi-go v0.0.23
github.com/armosec/armoapi-go v0.0.40
github.com/armosec/k8s-interface v0.0.50
github.com/armosec/opa-utils v0.0.75
github.com/armosec/rbac-utils v0.0.9
github.com/armosec/opa-utils v0.0.88
github.com/armosec/rbac-utils v0.0.10
github.com/armosec/utils-go v0.0.3
github.com/briandowns/spinner v1.18.0
github.com/enescakir/emoji v1.0.0

11
go.sum
View File

@@ -84,18 +84,19 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
github.com/armosec/armoapi-go v0.0.23 h1:jqoLIWM5CR7DCD9fpFgN0ePqtHvOCoZv/XzCwsUluJU=
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.40 h1:KQRJXFqw95s6cV7HoGgw1x8qrRZ9eNVze//yQbo24Lk=
github.com/armosec/armoapi-go v0.0.40/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
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.50 h1:iLPGI0j85vwKANr9QDAnba4Efjg3DyIJg15jRJdvOnc=
github.com/armosec/k8s-interface v0.0.50/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.75 h1:GBI3K18xc3WXJHIorIu4bGNAsfMYHUc1x7zueDz2ZbY=
github.com/armosec/opa-utils v0.0.75/go.mod h1:L7d+uiIIXAZ3LEyKtmEIbMcI1hWgWaXGpn5zVCqzwSU=
github.com/armosec/opa-utils v0.0.88 h1:IxIml3w7l0HFqbb+XzKuXf+Pw78DHIxPwRIkgudKQRw=
github.com/armosec/opa-utils v0.0.88/go.mod h1:ZOXYVTtuyrV4TldcfbzgRqP6F9Drlf4hB0zr210OXgM=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.9 h1:rIOWp4K7BELUNX32ktSjVbb8d/0SpH7W76W6Tf+8rzw=
github.com/armosec/rbac-utils v0.0.9/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
github.com/armosec/rbac-utils v0.0.10 h1:bFjesO8+xJS1ryR9vqj4xFEo1cQ0HvClzR+LWHzozW4=
github.com/armosec/rbac-utils v0.0.10/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
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

@@ -104,7 +104,22 @@ func (hsh *HostSensorHandler) GetLinuxSecurityHardeningStatus() ([]hostsensor.Ho
// return list of KubeletCommandLine
func (hsh *HostSensorHandler) GetKubeletCommandLine() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
return hsh.sendAllPodsHTTPGETRequest("/kubeletCommandLine", "KubeletCommandLine")
resps, err := hsh.sendAllPodsHTTPGETRequest("/kubeletCommandLine", "KubeletCommandLine")
if err != nil {
return resps, err
}
for resp := range resps {
var data = make(map[string]interface{})
data["fullCommand"] = string(resps[resp].Data)
resBytesMarshal, err := json.Marshal(data)
// TODO catch error
if err == nil {
resps[resp].Data = json.RawMessage(resBytesMarshal)
}
}
return resps, nil
}
// return list of
@@ -122,7 +137,7 @@ func (hsh *HostSensorHandler) GetOsReleaseFile() ([]hostsensor.HostSensorDataEnv
// return list of
func (hsh *HostSensorHandler) GetKubeletConfigurations() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
res, err := hsh.sendAllPodsHTTPGETRequest("/kubeletConfigurations", "KubeletConfigurations") // empty kind, will be overridden
res, err := hsh.sendAllPodsHTTPGETRequest("/kubeletConfigurations", "KubeletConfiguration") // empty kind, will be overridden
for resIdx := range res {
jsonBytes, err := yaml.YAMLToJSON(res[resIdx].Data)
if err != nil {

85
mocks/loadmocks.go Normal file

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,9 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/score"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
"github.com/open-policy-agent/opa/storage"
"github.com/golang/glog"
@@ -18,7 +20,6 @@ import (
"github.com/armosec/opa-utils/resources"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
uuid "github.com/satori/go.uuid"
)
const ScoreConfigPath = "/resources/config"
@@ -58,41 +59,50 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
opaSessionObj := <-*opaHandler.processedPolicy
opap := NewOPAProcessor(opaSessionObj, opaHandler.regoDependenciesData)
ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Frameworks)
policies := ConvertFrameworksToPolicies(opap.Frameworks, cautils.BuildNumber)
// process
if err := opap.Process(); err != nil {
if err := opap.Process(policies); err != nil {
// fmt.Println(err)
}
// edit results
opap.updateResults()
// update score
scoreutil := score.NewScore(opaSessionObj.AllResources)
scoreutil.Calculate(opaSessionObj.PostureReport.FrameworkReports)
// report
*opaHandler.reportResults <- opaSessionObj
}
}
func (opap *OPAProcessor) Process() error {
func (opap *OPAProcessor) Process(policies *cautils.Policies) error {
// glog.Infof(fmt.Sprintf("Starting 'Process'. reportID: %s", opap.PostureReport.ReportID))
cautils.ProgressTextDisplay(fmt.Sprintf("Scanning cluster %s", cautils.ClusterName))
cautils.StartSpinner()
frameworkReports := []reporthandling.FrameworkReport{}
var errs error
for i := range opap.Frameworks {
frameworkReport, err := opap.processFramework(&opap.Frameworks[i])
for _, control := range policies.Controls {
resourcesAssociatedControl, err := opap.processControl(&control)
if err != nil {
appendError(&errs, err)
}
frameworkReports = append(frameworkReports, *frameworkReport)
// update resources with latest results
if len(resourcesAssociatedControl) != 0 {
for resourceID, controlResult := range resourcesAssociatedControl {
if _, ok := opap.ResourcesResult[resourceID]; !ok {
opap.ResourcesResult[resourceID] = resourcesresults.Result{ResourceID: resourceID}
}
t := opap.ResourcesResult[resourceID]
t.AssociatedControls = append(t.AssociatedControls, controlResult)
opap.ResourcesResult[resourceID] = t
}
}
}
opap.PostureReport.FrameworkReports = frameworkReports
opap.PostureReport.ReportID = uuid.NewV4().String()
opap.PostureReport.ReportGenerationTime = time.Now().UTC()
// glog.Infof(fmt.Sprintf("Done 'Process'. reportID: %s", opap.PostureReport.ReportID))
opap.Report.ReportGenerationTime = time.Now().UTC()
cautils.StopSpinner()
cautils.SuccessTextDisplay(fmt.Sprintf("Done scanning cluster %s", cautils.ClusterName))
return errs
@@ -108,162 +118,142 @@ func appendError(errs *error, err error) {
*errs = fmt.Errorf("%v\n%s", *errs, err.Error())
}
}
func (opap *OPAProcessor) processFramework(framework *reporthandling.Framework) (*reporthandling.FrameworkReport, error) {
func (opap *OPAProcessor) processControl(control *reporthandling.Control) (map[string]resourcesresults.ResourceAssociatedControl, error) {
var errs error
frameworkReport := reporthandling.FrameworkReport{}
frameworkReport.Name = framework.Name
resourcesAssociatedControl := make(map[string]resourcesresults.ResourceAssociatedControl)
controlReports := []reporthandling.ControlReport{}
for i := range framework.Controls {
controlReport, err := opap.processControl(&framework.Controls[i])
if err != nil {
appendError(&errs, err)
// errs = fmt.Errorf("%v\n%s", errs, err.Error())
}
if controlReport != nil {
controlReports = append(controlReports, *controlReport)
}
}
frameworkReport.ControlReports = controlReports
return &frameworkReport, errs
}
func (opap *OPAProcessor) processControl(control *reporthandling.Control) (*reporthandling.ControlReport, error) {
var errs error
controlReport := reporthandling.ControlReport{}
controlReport.PortalBase = control.PortalBase
controlReport.ControlID = control.ControlID
controlReport.BaseScore = control.BaseScore
controlReport.Control_ID = control.Control_ID // TODO: delete when 'id' is deprecated
controlReport.Name = control.Name
controlReport.Description = control.Description
controlReport.Remediation = control.Remediation
ruleReports := []reporthandling.RuleReport{}
// ruleResults := make(map[string][]resourcesresults.ResourceAssociatedRule)
for i := range control.Rules {
ruleReport, err := opap.processRule(&control.Rules[i])
resourceAssociatedRule, err := opap.processRule(&control.Rules[i])
if err != nil {
appendError(&errs, err)
continue
}
if ruleReport != nil {
ruleReports = append(ruleReports, *ruleReport)
// append failed rules to controls
if len(resourceAssociatedRule) != 0 {
for resourceID, ruleResponse := range resourceAssociatedRule {
controlResult := resourcesresults.ResourceAssociatedControl{}
controlResult.SetID(control.ControlID)
controlResult.SetName(control.Name)
if _, ok := resourcesAssociatedControl[resourceID]; ok {
controlResult.ResourceAssociatedRules = resourcesAssociatedControl[resourceID].ResourceAssociatedRules
}
if ruleResponse != nil {
controlResult.ResourceAssociatedRules = append(controlResult.ResourceAssociatedRules, *ruleResponse)
}
resourcesAssociatedControl[resourceID] = controlResult
}
}
}
if len(ruleReports) == 0 {
return nil, nil
}
controlReport.RuleReports = ruleReports
return &controlReport, errs
return resourcesAssociatedControl, errs
}
func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule) (*reporthandling.RuleReport, error) {
if ruleWithArmoOpaDependency(rule.Attributes) || !isRuleKubescapeVersionCompatible(rule) {
return nil, nil
}
func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule) (map[string]*resourcesresults.ResourceAssociatedRule, error) {
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs) // get store
inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.AllResources, rule))
if err != nil {
return nil, fmt.Errorf("error getting aggregated k8sObjects: %s", err.Error())
}
if len(inputResources) == 0 {
return nil, nil // no resources found for testing
}
inputRawResources := workloadinterface.ListMetaToMap(inputResources)
ruleReport, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData)
if err != nil {
// ruleReport.RuleStatus.Status = reporthandling.StatusFailed
ruleReport.RuleStatus.Status = "failure"
ruleReport.RuleStatus.Message = err.Error()
glog.Error(err)
} else {
ruleReport.RuleStatus.Status = reporthandling.StatusPassed
}
resources := map[string]*resourcesresults.ResourceAssociatedRule{}
// the failed resources are a subgroup of the enumeratedData, so we store the enumeratedData like it was the input data
enumeratedData, err := opap.enumerateData(rule, inputRawResources)
if err != nil {
return nil, err
}
inputResources = objectsenvelopes.ListMapToMeta(enumeratedData)
ruleReport.ListInputKinds = workloadinterface.ListMetaIDs(inputResources)
for i := range inputResources {
resources[inputResources[i].GetID()] = &resourcesresults.ResourceAssociatedRule{
Name: rule.Name,
ControlConfigurations: postureControlInputs,
Status: apis.StatusPassed,
}
opap.AllResources[inputResources[i].GetID()] = inputResources[i]
}
failedResources := objectsenvelopes.ListMapToMeta(ruleReport.GetFailedResources())
for i := range failedResources {
if r, ok := opap.AllResources[failedResources[i].GetID()]; !ok {
opap.AllResources[failedResources[i].GetID()] = r
}
}
warningResources := objectsenvelopes.ListMapToMeta(ruleReport.GetWarnignResources())
for i := range warningResources {
if r, ok := opap.AllResources[warningResources[i].GetID()]; !ok {
opap.AllResources[warningResources[i].GetID()] = r
ruleResponses, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData, postureControlInputs)
if err != nil {
// TODO - Handle error
glog.Error(err)
} else {
// ruleResponse to ruleResult
for i := range ruleResponses {
failedResources := objectsenvelopes.ListMapToMeta(ruleResponses[i].GetFailedResources())
for j := range failedResources {
ruleResult := &resourcesresults.ResourceAssociatedRule{}
if r, k := resources[failedResources[j].GetID()]; k {
ruleResult = r
}
ruleResult.Status = apis.StatusFailed
for j := range ruleResponses[i].FailedPaths {
ruleResult.Paths = append(ruleResult.Paths, resourcesresults.Path{FailedPath: ruleResponses[i].FailedPaths[j]})
}
resources[failedResources[j].GetID()] = ruleResult
}
}
}
// remove all data from responses, leave only the metadata
keepFields := []string{"kind", "apiVersion", "metadata"}
keepMetadataFields := []string{"name", "namespace", "labels"}
ruleReport.RemoveData(keepFields, keepMetadataFields)
return &ruleReport, err
return resources, err
}
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string) (reporthandling.RuleReport, error) {
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
switch rule.RuleLanguage {
case reporthandling.RegoLanguage, reporthandling.RegoLanguage2:
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData)
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData, postureControlInputs)
default:
return reporthandling.RuleReport{}, fmt.Errorf("rule: '%s', language '%v' not supported", rule.Name, rule.RuleLanguage)
return nil, fmt.Errorf("rule: '%s', language '%v' not supported", rule.Name, rule.RuleLanguage)
}
}
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string) (reporthandling.RuleReport, error) {
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
var errs error
ruleReport := reporthandling.RuleReport{
Name: rule.Name,
}
// compile modules
modules, err := getRuleDependencies()
if err != nil {
return ruleReport, fmt.Errorf("rule: '%s', %s", rule.Name, err.Error())
return nil, fmt.Errorf("rule: '%s', %s", rule.Name, err.Error())
}
modules[rule.Name] = getRuleData(rule)
compiled, err := ast.CompileModules(modules)
if err != nil {
return ruleReport, fmt.Errorf("in 'runRegoOnSingleRule', failed to compile rule, name: %s, reason: %s", rule.Name, err.Error())
return nil, fmt.Errorf("in 'runRegoOnSingleRule', failed to compile rule, name: %s, reason: %s", rule.Name, err.Error())
}
store, err := resources.TOStorage(postureControlInputs)
if err != nil {
return nil, err
}
// Eval
results, err := opap.regoEval(k8sObjects, compiled)
results, err := opap.regoEval(k8sObjects, compiled, &store)
if err != nil {
errs = fmt.Errorf("rule: '%s', %s", rule.Name, err.Error())
}
if results != nil {
ruleReport.RuleResponses = append(ruleReport.RuleResponses, results...)
}
return ruleReport, errs
return results, errs
}
func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRego *ast.Compiler) ([]reporthandling.RuleResponse, error) {
store, err := opap.regoDependenciesData.TOStorage() // get store
if err != nil {
return nil, err
}
func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRego *ast.Compiler, store *storage.Store) ([]reporthandling.RuleResponse, error) {
// opap.regoDependenciesData.PostureControlInputs
rego := rego.New(
rego.Query("data.armo_builtins"), // get package name from rule
rego.Compiler(compiledRego),
rego.Input(inputObj),
rego.Store(store),
rego.Store(*store),
)
// Run evaluation
@@ -284,9 +274,15 @@ func (opap *OPAProcessor) enumerateData(rule *reporthandling.PolicyRule, k8sObje
if ruleEnumeratorData(rule) == "" {
return k8sObjects, nil
}
ruleReport, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData)
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs)
ruleResponse, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData, postureControlInputs)
if err != nil {
return nil, err
}
return ruleReport.GetFailedResources(), nil
failedResources := []map[string]interface{}{}
for _, ruleResponse := range ruleResponse {
failedResources = append(failedResources, ruleResponse.GetFailedResources()...)
}
return failedResources, nil
}

View File

@@ -3,10 +3,13 @@ package opaprocessor
import (
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/mocks"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/resources"
"github.com/stretchr/testify/assert"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
@@ -30,11 +33,13 @@ func TestProcess(t *testing.T) {
// set opaSessionObj
opaSessionObj := cautils.NewOPASessionObjMock()
opaSessionObj.Frameworks = []reporthandling.Framework{*reporthandling.MockFrameworkA()}
policies := ConvertFrameworksToPolicies(opaSessionObj.Frameworks, "")
opaSessionObj.K8SResources = &k8sResources
opaSessionObj.AllResources = allResources
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock())
opap.Process()
opap.Process(policies)
opap.updateResults()
for _, f := range opap.PostureReport.FrameworkReports {
for _, c := range f.ControlReports {
@@ -50,3 +55,85 @@ func TestProcess(t *testing.T) {
}
}
func TestProcessResourcesResult(t *testing.T) {
// set k8s
k8sResources := make(cautils.K8SResources)
deployment := mocks.MockDevelopmentWithHostpath()
frameworks := []reporthandling.Framework{*mocks.MockFramework_0006_0013()}
k8sResources["apps/v1/deployments"] = workloadinterface.ListMetaIDs([]workloadinterface.IMetadata{deployment})
// set opaSessionObj
opaSessionObj := cautils.NewOPASessionObjMock()
opaSessionObj.Frameworks = frameworks
policies := ConvertFrameworksToPolicies(opaSessionObj.Frameworks, "")
ConvertFrameworksToSummaryDetails(&opaSessionObj.Report.SummaryDetails, opaSessionObj.Frameworks)
opaSessionObj.K8SResources = &k8sResources
opaSessionObj.AllResources[deployment.GetID()] = deployment
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock())
opap.Process(policies)
assert.Equal(t, 1, len(opaSessionObj.ResourcesResult))
res := opaSessionObj.ResourcesResult[deployment.GetID()]
assert.Equal(t, 2, len(res.ListControlsIDs(nil).All()))
assert.Equal(t, 1, len(res.ListControlsIDs(nil).Failed()))
assert.Equal(t, 1, len(res.ListControlsIDs(nil).Passed()))
assert.True(t, res.GetStatus(nil).IsFailed())
assert.False(t, res.GetStatus(nil).IsPassed())
assert.Equal(t, deployment.GetID(), opaSessionObj.ResourcesResult[deployment.GetID()].ResourceID)
opap.updateResults()
res = opaSessionObj.ResourcesResult[deployment.GetID()]
assert.Equal(t, 2, len(res.ListControlsIDs(nil).All()))
assert.Equal(t, 2, len(res.ListControlsIDs(nil).All()))
assert.Equal(t, 1, len(res.ListControlsIDs(nil).Failed()))
assert.Equal(t, 1, len(res.ListControlsIDs(nil).Passed()))
assert.True(t, res.GetStatus(nil).IsFailed())
assert.False(t, res.GetStatus(nil).IsPassed())
assert.Equal(t, deployment.GetID(), opaSessionObj.ResourcesResult[deployment.GetID()].ResourceID)
// test resource counters
summaryDetails := opaSessionObj.Report.SummaryDetails
assert.Equal(t, 1, summaryDetails.NumberOfResources().All())
assert.Equal(t, 1, summaryDetails.NumberOfResources().Failed())
assert.Equal(t, 0, summaryDetails.NumberOfResources().Excluded())
assert.Equal(t, 0, summaryDetails.NumberOfResources().Passed())
// test resource listing
assert.Equal(t, 1, len(summaryDetails.ListResourcesIDs().All()))
assert.Equal(t, 1, len(summaryDetails.ListResourcesIDs().Failed()))
assert.Equal(t, 0, len(summaryDetails.ListResourcesIDs().Excluded()))
assert.Equal(t, 0, len(summaryDetails.ListResourcesIDs().Passed()))
// test control listing
assert.Equal(t, len(res.ListControlsIDs(nil).All()), len(summaryDetails.ListControls().All()))
assert.Equal(t, len(res.ListControlsIDs(nil).Passed()), len(summaryDetails.ListControls().Passed()))
assert.Equal(t, len(res.ListControlsIDs(nil).Failed()), len(summaryDetails.ListControls().Failed()))
assert.Equal(t, len(res.ListControlsIDs(nil).Excluded()), len(summaryDetails.ListControls().Excluded()))
assert.True(t, summaryDetails.GetStatus().IsFailed())
opaSessionObj.Exceptions = []armotypes.PostureExceptionPolicy{*mocks.MockExceptionAllKinds(&armotypes.PosturePolicy{FrameworkName: frameworks[0].Name})}
opap.updateResults()
res = opaSessionObj.ResourcesResult[deployment.GetID()]
assert.Equal(t, 2, len(res.ListControlsIDs(nil).All()))
assert.Equal(t, 1, len(res.ListControlsIDs(nil).Excluded()))
assert.Equal(t, 1, len(res.ListControlsIDs(nil).Passed()))
assert.True(t, res.GetStatus(nil).IsExcluded())
assert.False(t, res.GetStatus(nil).IsPassed())
assert.False(t, res.GetStatus(nil).IsFailed())
assert.Equal(t, deployment.GetID(), opaSessionObj.ResourcesResult[deployment.GetID()].ResourceID)
// test resource listing
summaryDetails = opaSessionObj.Report.SummaryDetails
assert.Equal(t, 1, len(summaryDetails.ListResourcesIDs().All()))
assert.Equal(t, 1, len(summaryDetails.ListResourcesIDs().Failed()))
assert.Equal(t, 0, len(summaryDetails.ListResourcesIDs().Excluded()))
assert.Equal(t, 0, len(summaryDetails.ListResourcesIDs().Passed()))
}

View File

@@ -7,29 +7,58 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/exceptions"
"github.com/armosec/opa-utils/reporthandling"
resources "github.com/armosec/opa-utils/resources"
"github.com/golang/glog"
)
// updateResults update the results objects and report objects. This is a critical function - DO NOT CHANGE
/*
- remove sensible data
- adding exceptions
- summarize results
*/
func (opap *OPAProcessor) updateResults() {
// remove data from all objects
for i := range opap.AllResources {
removeData(opap.AllResources[i])
}
for f := range opap.PostureReport.FrameworkReports {
// set exceptions
exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName)
// set exceptions
for i := range opap.ResourcesResult {
// set counters
reporthandling.SetUniqueResourcesCounter(&opap.PostureReport.FrameworkReports[f])
t := opap.ResourcesResult[i]
// set default score
// reporthandling.SetDefaultScore(&opap.PostureReport.FrameworkReports[f])
// first set exceptions
if resource, ok := opap.AllResources[i]; ok {
t.SetExceptions(resource, opap.Exceptions, cautils.ClusterName)
}
// summarize the resources
opap.Report.AppendResourceResultToSummary(&t)
// Add score
// TODO
// save changes
opap.ResourcesResult[i] = t
}
// set result summary
opap.Report.SummaryDetails.InitResourcesSummary()
// for f := range opap.PostureReport.FrameworkReports {
// // set exceptions
// exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName)
// // set counters
// reporthandling.SetUniqueResourcesCounter(&opap.PostureReport.FrameworkReports[f])
// // set default score
// // reporthandling.SetDefaultScore(&opap.PostureReport.FrameworkReports[f])
// }
}
func getAllSupportedObjects(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, rule *reporthandling.PolicyRule) []workloadinterface.IMetadata {
@@ -41,6 +70,7 @@ func getAllSupportedObjects(k8sResources *cautils.K8SResources, allResources map
func getKubernetesObjects(k8sResources *cautils.K8SResources, 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 {
@@ -62,9 +92,33 @@ func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[s
}
}
return k8sObjects
return filterOutChildResources(k8sObjects, match)
}
// filterOutChildResources filter out child resources if the parent resource is in the list
func filterOutChildResources(objects []workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata {
response := []workloadinterface.IMetadata{}
owners := []string{}
for m := range match {
for i := range match[m].Resources {
owners = append(owners, match[m].Resources[i])
}
}
for i := range objects {
if !k8sinterface.IsTypeWorkload(objects[i].GetObject()) {
response = append(response, objects[i])
continue
}
w := workloadinterface.NewWorkloadObj(objects[i].GetObject())
ownerReferences, err := w.GetOwnerReferences()
if err != nil || len(ownerReferences) == 0 {
response = append(response, w)
} else if !k8sinterface.IsStringInSlice(owners, ownerReferences[0].Kind) {
response = append(response, w)
}
}
return response
}
func getRuleDependencies() (map[string]string, error) {
modules := resources.LoadRegoModules()
if len(modules) == 0 {

View File

@@ -6,7 +6,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
@@ -58,7 +57,6 @@ func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
}
func TestRemoveData(t *testing.T) {
k8sinterface.InitializeMapResourcesMock()
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"demoservice-server"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"demoservice-server"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}}}`
obj, _ := workloadinterface.NewWorkload([]byte(w))

43
opaprocessor/utils.go Normal file
View File

@@ -0,0 +1,43 @@
package opaprocessor
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
)
// ConvertFrameworksToPolicies convert list of frameworks to list of policies
func ConvertFrameworksToPolicies(frameworks []reporthandling.Framework, version string) *cautils.Policies {
policies := cautils.NewPolicies()
policies.Set(frameworks, version)
return policies
}
// ConvertFrameworksToSummaryDetails initialize the summary details for the report object
func ConvertFrameworksToSummaryDetails(summaryDetails *reportsummary.SummaryDetails, frameworks []reporthandling.Framework) {
if summaryDetails.Controls == nil {
summaryDetails.Controls = make(map[string]reportsummary.ControlSummary)
}
for i := range frameworks {
controls := map[string]reportsummary.ControlSummary{}
for j := range frameworks[i].Controls {
id := frameworks[i].Controls[j].ControlID
c := reportsummary.ControlSummary{
Name: frameworks[i].Controls[j].Name,
ControlID: id,
ScoreFactor: frameworks[i].Controls[j].BaseScore,
Description: frameworks[i].Controls[j].Description,
Remediation: frameworks[i].Controls[j].Remediation,
}
controls[frameworks[i].Controls[j].ControlID] = c
summaryDetails.Controls[id] = c
}
if frameworks[i].Name != "" {
summaryDetails.Frameworks = append(summaryDetails.Frameworks, reportsummary.FrameworkSummary{
Name: frameworks[i].Name,
Controls: controls,
})
}
}
}

View File

@@ -0,0 +1,29 @@
package opaprocessor
import (
"testing"
"github.com/armosec/kubescape/mocks"
"github.com/stretchr/testify/assert"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
)
func TestConvertFrameworksToPolicies(t *testing.T) {
fw0 := mocks.MockFramework_0006_0013()
fw1 := mocks.MockFramework_0044()
policies := ConvertFrameworksToPolicies([]reporthandling.Framework{*fw0, *fw1}, "")
assert.Equal(t, 2, len(policies.Frameworks))
assert.Equal(t, 3, len(policies.Controls))
}
func TestInitializeSummaryDetails(t *testing.T) {
fw0 := mocks.MockFramework_0006_0013()
fw1 := mocks.MockFramework_0044()
summaryDetails := reportsummary.SummaryDetails{}
frameworks := []reporthandling.Framework{*fw0, *fw1}
ConvertFrameworksToSummaryDetails(&summaryDetails, frameworks)
assert.Equal(t, 2, len(summaryDetails.Frameworks))
assert.Equal(t, 3, len(summaryDetails.Controls))
}

View File

@@ -213,7 +213,11 @@ func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, []error) {
}
if obj, ok := j.(map[string]interface{}); ok {
if o := objectsenvelopes.NewObject(obj); o != nil {
yamlObjs = append(yamlObjs, o)
if o.GetKind() == "List" {
yamlObjs = append(yamlObjs, handleListObject(o)...)
} else {
yamlObjs = append(yamlObjs, o)
}
}
} else {
errs = append(errs, fmt.Errorf("failed to convert yaml file to map[string]interface, file content: %v", j))
@@ -303,3 +307,20 @@ func getFileFormat(filePath string) FileFormat {
return FileFormat(filePath)
}
}
// handleListObject handle a List manifest
func handleListObject(obj workloadinterface.IMetadata) []workloadinterface.IMetadata {
yamlObjs := []workloadinterface.IMetadata{}
if i, ok := workloadinterface.InspectMap(obj.GetObject(), "items"); ok && i != nil {
if items, ok := i.([]interface{}); ok && items != nil {
for item := range items {
if m, ok := items[item].(map[string]interface{}); ok && m != nil {
if o := objectsenvelopes.NewObject(m); o != nil {
yamlObjs = append(yamlObjs, o)
}
}
}
}
}
return yamlObjs
}

View File

@@ -61,14 +61,14 @@ func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.F
return k8sResourcesMap, allResources, err
}
if err := k8sHandler.collectHostResources(allResources, k8sResourcesMap); err != nil {
return k8sResourcesMap, allResources, err
cautils.WarningDisplay(os.Stderr, "Warning: failed to collect host sensor resources\n")
}
if err := k8sHandler.collectRbacResources(allResources); err != nil {
cautils.WarningDisplay(os.Stdout, "Warning: failed to collect rbac resources\n")
cautils.WarningDisplay(os.Stderr, "Warning: failed to collect rbac resources\n")
}
if err := getCloudProviderDescription(allResources, k8sResourcesMap); err != nil {
cautils.WarningDisplay(os.Stdout, fmt.Sprintf("Warning: %v\n", err.Error()))
cautils.WarningDisplay(os.Stderr, fmt.Sprintf("Warning: %v\n", err.Error()))
}
cautils.StopSpinner()
@@ -105,7 +105,7 @@ func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SRes
continue
}
// store result as []map[string]interface{}
metaObjs := ConvertMapListToMeta(k8sinterface.ConvertUnstructuredSliceToMap(k8sinterface.FilterOutOwneredResources(result)))
metaObjs := ConvertMapListToMeta(k8sinterface.ConvertUnstructuredSliceToMap(result))
for i := range metaObjs {
allResources[metaObjs[i].GetID()] = metaObjs[i]
}

View File

@@ -1,6 +1,9 @@
package printer
import (
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
)
@@ -17,17 +20,21 @@ type IPrinter interface {
ActionPrint(opaSessionObj *cautils.OPASessionObj)
SetWriter(outputFile string)
Score(score float32)
// FinalizeData convert 'opaSessionObj' data to be ready for printing/reporting
FinalizeData(opaSessionObj *cautils.OPASessionObj)
}
func GetPrinter(printFormat string, verboseMode bool) IPrinter {
switch printFormat {
case JsonFormat:
return NewJsonPrinter()
case JunitResultFormat:
return NewJunitPrinter()
case PrometheusFormat:
return NewPrometheusPrinter(verboseMode)
default:
return NewPrettyPrinter(verboseMode)
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("failed to open file for writing, reason: ", err.Error())
return os.Stdout
}
return f
}
return os.Stdout
}

View File

@@ -1,4 +1,4 @@
package printer
package v1
import (
"encoding/json"
@@ -6,6 +6,7 @@ import (
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
)
type JsonPrinter struct {
@@ -17,11 +18,11 @@ func NewJsonPrinter() *JsonPrinter {
}
func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
jsonPrinter.writer = getWriter(outputFile)
jsonPrinter.writer = printer.GetWriter(outputFile)
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Printf("\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
@@ -40,3 +41,6 @@ func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj
}
jsonPrinter.writer.Write(postureReportStr)
}
func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}

View File

@@ -1,4 +1,4 @@
package printer
package v1
import (
"encoding/xml"
@@ -6,6 +6,7 @@ import (
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling"
)
@@ -18,11 +19,15 @@ func NewJunitPrinter() *JunitPrinter {
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = getWriter(outputFile)
junitPrinter.writer = printer.GetWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Printf("\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {

View File

@@ -1,4 +1,4 @@
package printer
package v1
import (
"fmt"
@@ -8,6 +8,7 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
@@ -29,7 +30,7 @@ func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
}
}
func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
// score := calculatePostureScore(opaSessionObj.PostureReport)
failedResources := []string{}
warningResources := []string{}
@@ -44,32 +45,35 @@ func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj)
failedResources = reporthandling.GetUniqueResourcesIDs(append(failedResources, frameworkReport.ListResourcesIDs().GetFailedResources()...))
warningResources = reporthandling.GetUniqueResourcesIDs(append(warningResources, frameworkReport.ListResourcesIDs().GetWarningResources()...))
allResources = reporthandling.GetUniqueResourcesIDs(append(allResources, frameworkReport.ListResourcesIDs().GetAllResources()...))
printer.summarySetup(frameworkReport, opaSessionObj.AllResources)
prettyPrinter.summarySetup(frameworkReport, opaSessionObj.AllResources)
overallRiskScore += frameworkReport.Score
}
overallRiskScore /= float32(len(opaSessionObj.PostureReport.FrameworkReports))
printer.frameworkSummary = ResultSummary{
prettyPrinter.frameworkSummary = ResultSummary{
RiskScore: overallRiskScore,
TotalResources: len(allResources),
TotalFailed: len(failedResources),
TotalWarning: len(warningResources),
}
printer.printResults()
printer.printSummaryTable(frameworkNames, frameworkScores)
prettyPrinter.printResults()
prettyPrinter.printSummaryTable(frameworkNames, frameworkScores)
}
func (printer *PrettyPrinter) SetWriter(outputFile string) {
printer.writer = getWriter(outputFile)
func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
prettyPrinter.writer = printer.GetWriter(outputFile)
}
func (printer *PrettyPrinter) Score(score float32) {
func (prettyPrinter *PrettyPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}
func (printer *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, allResources map[string]workloadinterface.IMetadata) {
func (prettyPrinter *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, allResources map[string]workloadinterface.IMetadata) {
for _, cr := range fr.ControlReports {
if len(cr.RuleReports) == 0 {
@@ -78,12 +82,12 @@ func (printer *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, al
workloadsSummary := listResultSummary(cr.RuleReports, allResources)
var passedWorkloads map[string][]WorkloadSummary
if printer.verboseMode {
if prettyPrinter.verboseMode {
passedWorkloads = groupByNamespaceOrKind(workloadsSummary, workloadSummaryPassed)
}
//controlSummary
printer.summary[cr.Name] = ResultSummary{
prettyPrinter.summary[cr.Name] = ResultSummary{
ID: cr.ControlID,
RiskScore: cr.Score,
TotalResources: cr.GetNumberOfResources(),
@@ -98,81 +102,81 @@ func (printer *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, al
}
}
printer.sortedControlNames = printer.getSortedControlsNames()
prettyPrinter.sortedControlNames = prettyPrinter.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 (prettyPrinter *PrettyPrinter) printResults() {
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
prettyPrinter.printTitle(prettyPrinter.sortedControlNames[i], &controlSummary)
prettyPrinter.printResources(&controlSummary)
if prettyPrinter.summary[prettyPrinter.sortedControlNames[i]].TotalResources > 0 {
prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], &controlSummary)
}
}
}
func (printer *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) {
cautils.SimpleDisplay(printer.writer, "Summary - ")
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarning)
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) {
cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.TotalWarning)
cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(printer.writer, "\n")
cautils.DescriptionDisplay(prettyPrinter.writer, "\n")
}
func (printer *PrettyPrinter) printTitle(controlName string, controlSummary *ResultSummary) {
cautils.InfoDisplay(printer.writer, "[control: %s - %s] ", controlName, getControlURL(controlSummary.ID))
func (prettyPrinter *PrettyPrinter) printTitle(controlName string, controlSummary *ResultSummary) {
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlName, getControlURL(controlSummary.ID))
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(printer.writer, "skipped %v\n", emoji.ConfusedFace)
cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed != 0 {
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace)
} else if controlSummary.TotalWarning != 0 {
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace)
} else {
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.Description)
}
func (printer *PrettyPrinter) printResources(controlSummary *ResultSummary) {
func (prettyPrinter *PrettyPrinter) printResources(controlSummary *ResultSummary) {
if len(controlSummary.FailedWorkloads) > 0 {
cautils.FailureDisplay(printer.writer, "Failed:\n")
printer.printGroupedResources(controlSummary.FailedWorkloads)
cautils.FailureDisplay(prettyPrinter.writer, "Failed:\n")
prettyPrinter.printGroupedResources(controlSummary.FailedWorkloads)
}
if len(controlSummary.ExcludedWorkloads) > 0 {
cautils.WarningDisplay(printer.writer, "Excluded:\n")
printer.printGroupedResources(controlSummary.ExcludedWorkloads)
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:\n")
prettyPrinter.printGroupedResources(controlSummary.ExcludedWorkloads)
}
if len(controlSummary.PassedWorkloads) > 0 {
cautils.SuccessDisplay(printer.writer, "Passed:\n")
printer.printGroupedResources(controlSummary.PassedWorkloads)
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:\n")
prettyPrinter.printGroupedResources(controlSummary.PassedWorkloads)
}
}
func (printer *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
func (prettyPrinter *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
indent := INDENT
for title, rsc := range workloads {
printer.printGroupedResource(indent, title, rsc)
prettyPrinter.printGroupedResource(indent, title, rsc)
}
}
func (printer *PrettyPrinter) printGroupedResource(indent string, title string, rsc []WorkloadSummary) {
func (prettyPrinter *PrettyPrinter) printGroupedResource(indent string, title string, rsc []WorkloadSummary) {
preIndent := indent
if title != "" {
cautils.SimpleDisplay(printer.writer, "%s%s\n", indent, title)
cautils.SimpleDisplay(prettyPrinter.writer, "%s%s\n", indent, title)
indent += indent
}
for r := range rsc {
relatedObjectsStr := generateRelatedObjectsStr(rsc[r])
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s %s\n", indent, rsc[r].resource.GetKind(), rsc[r].resource.GetName(), relatedObjectsStr))
cautils.SimpleDisplay(prettyPrinter.writer, fmt.Sprintf("%s%s - %s %s\n", indent, rsc[r].resource.GetKind(), rsc[r].resource.GetName(), relatedObjectsStr))
}
indent = preIndent
}
@@ -209,75 +213,60 @@ func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
}
func generateFooter(printer *PrettyPrinter) []string {
func generateFooter(prettyPrinter *PrettyPrinter) []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", printer.frameworkSummary.TotalFailed))
row = append(row, fmt.Sprintf("%d", printer.frameworkSummary.TotalWarning))
row = append(row, fmt.Sprintf("%d", printer.frameworkSummary.TotalResources))
row = append(row, fmt.Sprintf("%.2f%s", printer.frameworkSummary.RiskScore, "%"))
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalFailed))
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalWarning))
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalResources))
row = append(row, fmt.Sprintf("%.2f%s", prettyPrinter.frameworkSummary.RiskScore, "%"))
return row
}
func (printer *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) {
func (prettyPrinter *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) {
// For control scan framework will be nil
printer.printFramework(frameworksNames, frameworkScores)
prettyPrinter.printFramework(frameworksNames, frameworkScores)
summaryTable := tablewriter.NewWriter(printer.writer)
summaryTable := tablewriter.NewWriter(prettyPrinter.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))
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
}
summaryTable.SetFooter(generateFooter(printer))
summaryTable.SetFooter(generateFooter(prettyPrinter))
// summaryTable.SetFooter(generateFooter())
summaryTable.Render()
}
func (printer *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) {
func (prettyPrinter *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) {
if len(frameworksNames) == 1 {
cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0]))
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0]))
} else if len(frameworksNames) > 1 {
p := "FRAMEWORKS: "
for i := 0; i < len(frameworksNames)-1; i++ {
p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i])
}
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1])
cautils.InfoTextDisplay(printer.writer, p)
cautils.InfoTextDisplay(prettyPrinter.writer, p)
}
}
func (printer *PrettyPrinter) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(printer.summary))
for k := range printer.summary {
func (prettyPrinter *PrettyPrinter) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(prettyPrinter.summary))
for k := range prettyPrinter.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("failed to open file for writing, reason: ", err.Error())
return os.Stdout
}
return f
}
return os.Stdout
}
func getControlURL(controlID string) string {
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
}

View File

@@ -0,0 +1,18 @@
package v1
import "github.com/armosec/kubescape/resultshandling/printer"
var INDENT = " "
func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter {
switch printFormat {
case printer.JsonFormat:
return NewJsonPrinter()
case printer.JunitResultFormat:
return NewJunitPrinter()
case printer.PrometheusFormat:
return NewPrometheusPrinter(verboseMode)
default:
return NewPrettyPrinter(verboseMode)
}
}

View File

@@ -1,4 +1,4 @@
package printer
package v1
import (
"fmt"
@@ -6,6 +6,7 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling"
)
@@ -21,13 +22,17 @@ func NewPrometheusPrinter(verboseMode bool) *PrometheusPrinter {
}
func (prometheusPrinter *PrometheusPrinter) SetWriter(outputFile string) {
prometheusPrinter.writer = getWriter(outputFile)
prometheusPrinter.writer = printer.GetWriter(outputFile)
}
func (prometheusPrinter *PrometheusPrinter) Score(score float32) {
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", int(score))
}
func (prometheusPrinter *PrometheusPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}
func (printer *PrometheusPrinter) printResources(allResources map[string]workloadinterface.IMetadata, resourcesIDs *reporthandling.ResourcesIDs, frameworkName, controlName string) {
printer.printDetails(allResources, resourcesIDs.GetFailedResources(), frameworkName, controlName, "failed")
printer.printDetails(allResources, resourcesIDs.GetWarningResources(), frameworkName, controlName, "excluded")

View File

@@ -0,0 +1,153 @@
package v1
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
helpersv1 "github.com/armosec/opa-utils/reporthandling/helpers/v1"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
"github.com/armosec/opa-utils/score"
)
func reportV2ToV1(opaSessionObj *cautils.OPASessionObj) {
opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
frameworks := []reporthandling.FrameworkReport{}
if len(opaSessionObj.Report.SummaryDetails.Frameworks) > 0 {
for _, fwv2 := range opaSessionObj.Report.SummaryDetails.Frameworks {
fwv1 := reporthandling.FrameworkReport{}
fwv1.Name = fwv2.GetName()
fwv1.Score = fwv2.GetScore()
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, fwv2.GetName(), fwv2.Controls)...)
frameworks = append(frameworks, fwv1)
}
} else {
fwv1 := reporthandling.FrameworkReport{}
fwv1.Name = ""
fwv1.Score = 0
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, "", opaSessionObj.Report.SummaryDetails.Controls)...)
frameworks = append(frameworks, fwv1)
}
// remove unused data
opaSessionObj.Report = nil
opaSessionObj.ResourcesResult = nil
// setup counters and score
for f := range frameworks {
// // set exceptions
// exceptions.SetFrameworkExceptions(frameworks, opap.Exceptions, cautils.ClusterName)
// set counters
reporthandling.SetUniqueResourcesCounter(&frameworks[f])
// set default score
reporthandling.SetDefaultScore(&frameworks[f])
}
// update score
scoreutil := score.NewScore(opaSessionObj.AllResources)
scoreutil.Calculate(frameworks)
opaSessionObj.PostureReport.FrameworkReports = frameworks
// for i := range frameworks {
// for j := range frameworks[i].ControlReports {
// // frameworks[i].ControlReports[j].Score
// for w := range opaSessionObj.Report.SummaryDetails.Frameworks {
// if opaSessionObj.Report.SummaryDetails.Frameworks[w].Name == frameworks[i].Name {
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Score = frameworks[i].Score
// }
// if c, ok := opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID]; ok {
// c.Score = frameworks[i].ControlReports[j].Score
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID] = c
// }
// }
// if c, ok := opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID]; ok {
// c.Score = frameworks[i].ControlReports[j].Score
// opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID] = c
// }
// }
// }
}
func controlReportV2ToV1(opaSessionObj *cautils.OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
controlRepors := []reporthandling.ControlReport{}
for controlID, crv2 := range controls {
crv1 := reporthandling.ControlReport{}
crv1.ControlID = controlID
crv1.BaseScore = crv2.ScoreFactor
crv1.Name = crv2.GetName()
crv1.Score = crv2.GetScore()
// TODO - add fields
crv1.Description = crv2.Description
crv1.Remediation = crv2.Remediation
rulesv1 := initializeRuleList(&crv2, opaSessionObj.ResourcesResult)
for _, resourceID := range crv2.List().All() {
if result, ok := opaSessionObj.ResourcesResult[resourceID]; ok {
for _, rulev2 := range result.ListRulesOfControl(crv2.GetID(), "") {
rulev1 := rulesv1[rulev2.GetName()]
status := rulev2.GetStatus(&helpersv1.Filters{FrameworkNames: []string{frameworkName}})
if status.IsFailed() || status.IsExcluded() {
// rule response
ruleResponse := reporthandling.RuleResponse{}
ruleResponse.Rulename = rulev2.GetName()
for i := range rulev2.Paths {
ruleResponse.FailedPaths = append(ruleResponse.FailedPaths, rulev2.Paths[i].FailedPath)
}
ruleResponse.RuleStatus = string(status.Status())
if len(rulev2.Exception) > 0 {
ruleResponse.Exception = &rulev2.Exception[0]
}
if fullRessource, ok := opaSessionObj.AllResources[resourceID]; ok {
ruleResponse.AlertObject.K8SApiObjects = append(ruleResponse.AlertObject.K8SApiObjects, fullRessource.GetObject())
}
rulev1.RuleResponses = append(rulev1.RuleResponses, ruleResponse)
}
rulev1.ListInputKinds = append(rulev1.ListInputKinds, resourceID)
rulesv1[rulev2.GetName()] = rulev1
}
}
}
if len(rulesv1) > 0 {
for i := range rulesv1 {
crv1.RuleReports = append(crv1.RuleReports, rulesv1[i])
}
}
controlRepors = append(controlRepors, crv1)
}
return controlRepors
}
func initializeRuleList(crv2 *reportsummary.ControlSummary, resourcesResult map[string]resourcesresults.Result) map[string]reporthandling.RuleReport {
rulesv1 := map[string]reporthandling.RuleReport{} // ruleName: rules
for _, resourceID := range crv2.List().All() {
if result, ok := resourcesResult[resourceID]; ok {
for _, rulev2 := range result.ListRulesOfControl(crv2.GetID(), "") {
// add to rule
if _, ok := rulesv1[rulev2.GetName()]; !ok {
rulesv1[rulev2.GetName()] = reporthandling.RuleReport{
Name: rulev2.GetName(),
}
}
}
}
}
return rulesv1
}

View File

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

View File

@@ -1,4 +1,4 @@
package printer
package v1
import (
"fmt"

View File

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

View File

@@ -0,0 +1,41 @@
package v2
import (
"encoding/json"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
)
type JsonPrinter struct {
writer *os.File
}
func NewJsonPrinter() *JsonPrinter {
return &JsonPrinter{}
}
func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
jsonPrinter.writer = printer.GetWriter(outputFile)
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
postureReportStr, err := json.Marshal(opaSessionObj.Report)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
jsonPrinter.writer.Write(postureReportStr)
}
func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
}

View File

@@ -0,0 +1,129 @@
package v2
import (
"encoding/xml"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
type JunitPrinter struct {
writer *os.File
}
func NewJunitPrinter() *JunitPrinter {
return &JunitPrinter{}
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = printer.GetWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.Report)
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"`
}
// JUnitTestSuite is a single JUnit test suite which may contain many
// testcases.
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Tests int `xml:"tests,attr"`
Time string `xml:"time,attr"`
Name string `xml:"name,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
Properties []JUnitProperty `xml:"properties>property,omitempty"`
TestCases []JUnitTestCase `xml:"testcase"`
}
// JUnitTestCase is a single test case with its result.
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
Classname string `xml:"classname,attr"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
// JUnitSkipMessage contains the reason why a testcase was skipped.
type JUnitSkipMessage struct {
Message string `xml:"message,attr"`
}
// JUnitProperty represents a key/value pair used to define properties.
type JUnitProperty struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
// JUnitFailure contains data related to a failed test.
type JUnitFailure struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Contents string `xml:",chardata"`
}
func convertPostureReportToJunitResult(postureResult *reporthandlingv2.PostureReport) (*JUnitTestSuites, error) {
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
for _, framework := range postureResult.ListFrameworks().All() {
suite := JUnitTestSuite{
Name: framework.GetName(),
Resources: framework.NumberOfResources().All(),
Excluded: framework.NumberOfResources().Excluded(),
Failed: framework.NumberOfResources().Failed(),
}
for _, controlReports := range postureResult.ListControls().All() {
suite.Tests = suite.Tests + 1
testCase := JUnitTestCase{}
testCase.Name = controlReports.GetName()
testCase.Classname = "Kubescape"
testCase.Time = postureResult.ReportGenerationTime.String()
// if 0 < len(controlReports.RuleReports[0].RuleResponses) {
// testCase.Resources = controlReports.NumberOfResources().All()
// testCase.Excluded = controlReports.NumberOfResources().Excluded()
// testCase.Failed = controlReports.NumberOfResources().Failed()
// failure := JUnitFailure{}
// failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
// for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
// failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage)
// }
// testCase.Failure = &failure
// }
suite.TestCases = append(suite.TestCases, testCase)
}
juResult.Suites = append(juResult.Suites, suite)
}
return &juResult, nil
}

View File

@@ -0,0 +1,215 @@
package v2
import (
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
"github.com/olekukonko/tablewriter"
)
type PrettyPrinter struct {
writer *os.File
verboseMode bool
}
func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
return &PrettyPrinter{
verboseMode: verboseMode,
}
}
func (prettyPrinter *PrettyPrinter) printResourceTable(results []resourcesresults.Result) {
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
summaryTable.SetAutoWrapText(true)
summaryTable.SetAutoMergeCells(false)
// summaryTable.SetCenterSeparator("=")
// summaryTable.SetRowSeparator("*")
// summaryTable.
summaryTable.SetHeader(generateResourceHeader())
summaryTable.SetHeaderLine(true)
// For control scan framework will be nil
for i := range results {
// status := result.GetStatus(nil).Status()
resourceID := results[i].GetResourceID()
control := results[i].ListControls()
if raw := generateResourceRow(resourceID, control, prettyPrinter.verboseMode); len(raw) > 0 {
summaryTable.Append(raw)
}
}
// 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(prettyPrinter.sortedControlNames); i++ {
// controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
// summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
// }
// summaryTable.SetFooter(generateFooter(prettyPrinter))
summaryTable.Render()
}
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
prettyPrinter.printResourceTable(opaSessionObj.Report.Results)
// var overallRiskScore float32 = 0
// for _, frameworkReport := range opaSessionObj.PostureReport.FrameworkReports {
// frameworkNames = append(frameworkNames, frameworkReport.Name)
// frameworkScores = append(frameworkScores, frameworkReport.Score)
// failedResources = reporthandling.GetUniqueResourcesIDs(append(failedResources, frameworkReport.ListResourcesIDs().GetFailedResources()...))
// warningResources = reporthandling.GetUniqueResourcesIDs(append(warningResources, frameworkReport.ListResourcesIDs().GetWarningResources()...))
// allResources = reporthandling.GetUniqueResourcesIDs(append(allResources, frameworkReport.ListResourcesIDs().GetAllResources()...))
// prettyPrinter.summarySetup(frameworkReport, opaSessionObj.AllResources)
// overallRiskScore += frameworkReport.Score
// }
// overallRiskScore /= float32(len(opaSessionObj.PostureReport.FrameworkReports))
// prettyPrinter.frameworkSummary = ResultSummary{
// RiskScore: overallRiskScore,
// TotalResources: len(allResources),
// TotalFailed: len(failedResources),
// TotalWarning: len(warningResources),
// }
// prettyPrinter.printResults()
// prettyPrinter.printSummaryTable(frameworkNames, frameworkScores)
}
func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
prettyPrinter.writer = printer.GetWriter(outputFile)
}
func (prettyPrinter *PrettyPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
}
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}
// func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) {
// // cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
// // cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
// // cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.TotalWarning)
// // cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.TotalFailed)
// // cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.TotalResources)
// // if controlSummary.TotalFailed > 0 {
// // cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.Remediation)
// // }
// // cautils.DescriptionDisplay(prettyPrinter.writer, "\n")
// }
func generateResourceRow(resourceID string, controls []resourcesresults.ResourceAssociatedControl, verboseMode bool) []string {
row := []string{}
controlsNames := []string{}
statuses := []string{}
for i := range controls {
if !verboseMode && controls[i].GetStatus(nil).IsPassed() {
continue
}
if controls[i].GetName() == "" {
continue
}
controlsNames = append(controlsNames, controls[i].GetName())
statuses = append(statuses, string(controls[i].GetStatus(nil).Status()))
}
splitted := strings.Split(resourceID, "/")
if len(splitted) < 5 || len(controlsNames) == 0 {
return row
}
row = append(row, splitted[3])
row = append(row, splitted[4])
row = append(row, splitted[2])
row = append(row, strings.Join(controlsNames, "\n"))
row = append(row, strings.Join(statuses, "\n"))
return row
}
// func generateRow(control string, cs ResultSummary) []string {
// row := []string{control}
// row = append(row, cs.ToSlice()...)
// if cs.TotalResources != 0 {
// row = append(row, fmt.Sprintf("%d", int(cs.RiskScore))+"%")
// } else {
// row = append(row, "skipped")
// }
// return row
// }
func generateResourceHeader() []string {
return []string{"Kind", "Name", "Namespace", "Controls", "Statues"}
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
}
func generateFooter(prettyPrinter *PrettyPrinter) []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", prettyPrinter.frameworkSummary.TotalFailed))
// row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalWarning))
// row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalResources))
// row = append(row, fmt.Sprintf("%.2f%s", prettyPrinter.frameworkSummary.RiskScore, "%"))
return row
}
func (prettyPrinter *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) {
// For control scan framework will be nil
prettyPrinter.printFramework(frameworksNames, frameworkScores)
summaryTable := tablewriter.NewWriter(prettyPrinter.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(prettyPrinter.sortedControlNames); i++ {
// controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
// summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
// }
// summaryTable.SetFooter(generateFooter(prettyPrinter))
summaryTable.Render()
}
func (prettyPrinter *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) {
if len(frameworksNames) == 1 {
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0]))
} else if len(frameworksNames) > 1 {
p := "FRAMEWORKS: "
for i := 0; i < len(frameworksNames)-1; i++ {
p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i])
}
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1])
cautils.InfoTextDisplay(prettyPrinter.writer, p)
}
}
// func (prettyPrinter *PrettyPrinter) getSortedControlsNames() []string {
// controlNames := make([]string, 0, len(prettyPrinter.summary))
// for k := range prettyPrinter.summary {
// controlNames = append(controlNames, k)
// }
// sort.Strings(controlNames)
// return controlNames
// }
// func getControlURL(controlID string) string {
// return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
// }

View File

@@ -0,0 +1,18 @@
package v2
import "github.com/armosec/kubescape/resultshandling/printer"
var INDENT = " "
func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter {
switch printFormat {
case printer.JsonFormat:
return NewJsonPrinter()
case printer.JunitResultFormat:
return NewJunitPrinter()
// case printer.PrometheusFormat:
// return NewPrometheusPrinter(verboseMode)
default:
return NewPrettyPrinter(verboseMode)
}
}

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
package v2
import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
// finalizeV2Report finalize the results objects by copying data from map to lists
func finalizeReport(opaSessionObj *cautils.OPASessionObj) {
if len(opaSessionObj.Report.Results) == 0 {
opaSessionObj.Report.Results = make([]resourcesresults.Result, len(opaSessionObj.ResourcesResult))
finalizeResults(opaSessionObj.Report.Results, opaSessionObj.ResourcesResult)
opaSessionObj.ResourcesResult = nil
}
if len(opaSessionObj.Report.Resources) == 0 {
opaSessionObj.Report.Resources = make([]reporthandlingv2.Resource, len(opaSessionObj.AllResources))
finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.AllResources)
opaSessionObj.AllResources = nil
}
}
func finalizeResults(results []resourcesresults.Result, resourcesResult map[string]resourcesresults.Result) {
index := 0
for resourceID := range resourcesResult {
results[index] = resourcesResult[resourceID]
index++
}
}
func finalizeResources(resources []reporthandlingv2.Resource, allResources map[string]workloadinterface.IMetadata) {
index := 0
for resourceID := range allResources {
resources[index] = reporthandlingv2.Resource{
ResourceID: resourceID,
Object: allResources[resourceID],
}
index++
}
}

View File

@@ -0,0 +1,10 @@
package reporter
import "github.com/armosec/kubescape/cautils"
type IReport interface {
ActionSendReport(opaSessionObj *cautils.OPASessionObj) error
SetCustomerGUID(customerGUID string)
SetClusterName(clusterName string)
DisplayReportURL()
}

View File

@@ -1,4 +1,4 @@
package reporter
package v1
import (
"fmt"
@@ -26,5 +26,5 @@ func (reportMock *ReportMock) SetClusterName(clusterName string) {
func (reportMock *ReportMock) DisplayReportURL() {
message := fmt.Sprintf("\nYou can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here: https://%s/cli-signup \n", getter.GetArmoAPIConnector().GetFrontendURL())
cautils.InfoTextDisplay(os.Stdout, fmt.Sprintf("\n%s\n", message))
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n%s\n", message))
}

View File

@@ -1,4 +1,4 @@
package reporter
package v1
import (
"encoding/json"
@@ -11,17 +11,11 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
uuid "github.com/satori/go.uuid"
)
const MAX_REPORT_SIZE = 2097152 // 2 MB
type IReport interface {
ActionSendReport(opaSessionObj *cautils.OPASessionObj) error
SetCustomerGUID(customerGUID string)
SetClusterName(clusterName string)
DisplayReportURL()
}
type ReportEventReceiver struct {
httpClient *http.Client
clusterName string
@@ -44,8 +38,11 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
if report.customerGUID == "" || report.clusterName == "" {
return fmt.Errorf("missing accout ID or cluster name. AccountID: '%s', Cluster name: '%s'", report.customerGUID, report.clusterName)
return fmt.Errorf("missing account ID or cluster name. AccountID: '%s', Cluster name: '%s'", report.customerGUID, report.clusterName)
}
opaSessionObj.PostureReport.ReportID = uuid.NewV4().String()
opaSessionObj.PostureReport.CustomerGUID = report.clusterName
opaSessionObj.PostureReport.ClusterName = report.customerGUID
if err := report.prepareReport(opaSessionObj.PostureReport, opaSessionObj.AllResources); err != nil {
return err
@@ -130,7 +127,7 @@ func (report *ReportEventReceiver) DisplayReportURL() {
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" {
cautils.InfoTextDisplay(os.Stdout, fmt.Sprintf("\n\n%s %s/risk/%s\n(Account: %s)\n\n", message, u.String(), report.clusterName, report.customerGUID))
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s/risk/%s\n(Account: %s)\n\n", message, u.String(), report.clusterName, report.customerGUID))
return
}
u.Path = "account/sign-up"
@@ -139,5 +136,5 @@ func (report *ReportEventReceiver) DisplayReportURL() {
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
cautils.InfoTextDisplay(os.Stdout, fmt.Sprintf("\n\n%s %s\n\n", message, u.String()))
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s\n\n", message, u.String()))
}

View File

@@ -1,4 +1,4 @@
package reporter
package v1
import (
"net/url"

View File

@@ -0,0 +1,180 @@
package v2
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
uuid "github.com/satori/go.uuid"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
const MAX_REPORT_SIZE = 2097152 // 2 MB
type ReportEventReceiver struct {
httpClient *http.Client
clusterName string
customerGUID string
eventReceiverURL *url.URL
token string
customerAdminEMail string
}
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceiver {
return &ReportEventReceiver{
httpClient: &http.Client{},
clusterName: tenantConfig.ClusterName,
customerGUID: tenantConfig.CustomerGUID,
token: tenantConfig.Token,
customerAdminEMail: tenantConfig.CustomerAdminEMail,
}
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
if report.customerGUID == "" || report.clusterName == "" {
return fmt.Errorf("missing accout ID or cluster name. AccountID: '%s', Cluster name: '%s'", report.customerGUID, report.clusterName)
}
opaSessionObj.Report.ReportID = uuid.NewV4().String()
opaSessionObj.Report.CustomerGUID = report.clusterName
opaSessionObj.Report.ClusterName = report.customerGUID
if err := report.prepareReport(opaSessionObj.Report); err != nil {
return err
}
return nil
}
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
report.customerGUID = customerGUID
}
func (report *ReportEventReceiver) SetClusterName(clusterName string) {
report.clusterName = cautils.AdoptClusterName(clusterName) // clean cluster name
}
func (report *ReportEventReceiver) prepareReport(postureReport *reporthandlingv2.PostureReport) error {
report.initEventReceiverURL()
host := hostToString(report.eventReceiverURL, postureReport.ReportID)
cautils.StartSpinner()
defer cautils.StopSpinner()
// send framework results
if err := report.sendReport(host, postureReport); err != nil {
return err
}
// send resources
if err := report.sendResults(host, postureReport); err != nil {
return err
}
// send resources
if err := report.sendResources(host, postureReport); err != nil {
return err
}
return nil
}
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport) error {
splittedPostureReport := setSubReport(postureReport)
counter := 0
for _, v := range postureReport.Resources {
r, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.ResourceID, err)
}
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport); err != nil {
return err
}
// delete resources
splittedPostureReport.Resources = []reporthandlingv2.Resource{}
// restart counter
counter = 0
}
counter += len(r)
splittedPostureReport.Resources = append(splittedPostureReport.Resources, v)
}
return report.sendReport(host, splittedPostureReport)
}
func (report *ReportEventReceiver) sendResults(host string, postureReport *reporthandlingv2.PostureReport) error {
splittedPostureReport := setSubReport(postureReport)
counter := 0
for _, v := range postureReport.Results {
r, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
}
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport); err != nil {
return err
}
// delete results
splittedPostureReport.Results = []resourcesresults.Result{}
// restart counter
counter = 0
}
counter += len(r)
splittedPostureReport.Results = append(splittedPostureReport.Results, v)
}
return report.sendReport(host, splittedPostureReport)
}
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport) error {
splittedPostureReport := setSubReport(postureReport)
splittedPostureReport.SummaryDetails = postureReport.SummaryDetails
reqBody, err := json.Marshal(postureReport)
if err != nil {
return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
}
msg, err := getter.HttpPost(report.httpClient, host, nil, reqBody)
if err != nil {
return fmt.Errorf("%s, %v:%s", host, err, msg)
}
return err
}
func (report *ReportEventReceiver) DisplayReportURL() {
message := "You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here:"
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s/risk/%s\n(Account: %s)\n\n", message, u.String(), report.clusterName, report.customerGUID))
return
}
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s\n\n", message, u.String()))
}

View File

@@ -0,0 +1,48 @@
package v2
import (
"net/url"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/gofrs/uuid"
)
func (report *ReportEventReceiver) initEventReceiverURL() {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
q.Add("clusterName", report.clusterName)
urlObj.RawQuery = q.Encode()
report.eventReceiverURL = &urlObj
}
func hostToString(host *url.URL, reportID string) string {
q := host.Query()
q.Add("reportID", reportID) // TODO - do we add the reportID?
host.RawQuery = q.Encode()
return host.String()
}
func setSubReport(postureReport *reporthandlingv2.PostureReport) *reporthandlingv2.PostureReport {
return &reporthandlingv2.PostureReport{
CustomerGUID: postureReport.CustomerGUID,
ClusterName: postureReport.ClusterName,
ReportID: postureReport.ReportID,
ReportGenerationTime: postureReport.ReportGenerationTime,
}
}
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {
return &reporthandling.Resource{
ResourceID: obj.GetID(),
Object: obj.GetObject(),
}
}

View File

@@ -0,0 +1,20 @@
package v2
import (
"net/url"
"testing"
)
func TestHostToString(t *testing.T) {
host := url.URL{
Scheme: "https",
Host: "report.eudev3.cyberarmorsoft.com",
Path: "k8srestapi/v1/postureReport",
RawQuery: "cluster=openrasty_seal-7fvz&customerGUID=5d817063-096f-4d91-b39b-8665240080af",
}
expectedHost := "https://report.eudev3.cyberarmorsoft.com/k8srestapi/v1/postureReport?cluster=openrasty_seal-7fvz&customerGUID=5d817063-096f-4d91-b39b-8665240080af&reportID=ffdd2a00-4dc8-4bf3-b97a-a6d4fd198a41"
receivedHost := hostToString(&host, "ffdd2a00-4dc8-4bf3-b97a-a6d4fd198a41")
if receivedHost != expectedHost {
t.Errorf("%s != %s", receivedHost, expectedHost)
}
}

View File

@@ -27,6 +27,7 @@ func (resultsHandler *ResultsHandler) HandleResults(scanInfo *cautils.ScanInfo)
opaSessionObj := <-*resultsHandler.opaSessionObj
resultsHandler.printerObj.FinalizeData(opaSessionObj)
resultsHandler.printerObj.ActionPrint(opaSessionObj)
if err := resultsHandler.reporterObj.ActionSendReport(opaSessionObj); err != nil {

File diff suppressed because one or more lines are too long