Skip to content

Commit

Permalink
fix: limit the num of failure entries in compliance detail report (#1108
Browse files Browse the repository at this point in the history
)

* fix: limit the num of failure entries in compliance detail report

Signed-off-by: chenk <hen.keinan@gmail.com>

* test: update compliance constructor

Signed-off-by: chenk <hen.keinan@gmail.com>

* fix: add compliance fail limit flag to config map

Signed-off-by: chenk <hen.keinan@gmail.com>
  • Loading branch information
chen-keinan committed Apr 6, 2022
1 parent 4d5855a commit cb5f3b3
Show file tree
Hide file tree
Showing 13 changed files with 89 additions and 30 deletions.
3 changes: 3 additions & 0 deletions deploy/helm/templates/config.yaml
Expand Up @@ -24,6 +24,9 @@ data:
{{- if .Values.operator.kubernetesBenchmarkEnabled }}
kube-bench.imageRef: {{ required ".Values.kubeBench.imageRef is required" .Values.kubeBench.imageRef | quote }}
{{- end }}
{{- if .Values.operator.clusterComplianceEnabled }}
compliance.failEntriesLimit: {{ required ".Values.compliance.failEntriesLimit is required" .Values.compliance.failEntriesLimit | quote }}
{{- end }}
---
apiVersion: v1
kind: Secret
Expand Down
6 changes: 4 additions & 2 deletions deploy/helm/values.yaml
Expand Up @@ -47,7 +47,7 @@ operator:
configAuditScannerBuiltIn: true
# kubernetesBenchmarkEnabled the flag to enable CIS Kubernetes Benchmark scanner
kubernetesBenchmarkEnabled: true
# clusterComplianceEnabled the flag to enable CIS Kubernetes Benchmark scanner
# clusterComplianceEnabled the flag to enable cluster compliance report generation
clusterComplianceEnabled: true
# batchDeleteLimit the maximum number of config audit reports deleted by the operator when the plugin's config has changed.
batchDeleteLimit: 10
Expand Down Expand Up @@ -176,7 +176,9 @@ trivy:
# Trivy client to Trivy server. Only applicable in ClientServer mode.
#
# serverCustomHeaders: "foo=bar"

compliance:
# failEntriesLimit the flag to limit the number of fail entries per control check in the cluster compliance detail report
failEntriesLimit: 10
kubeBench:
imageRef: docker.io/aquasec/kube-bench:v0.6.6

Expand Down
1 change: 1 addition & 0 deletions deploy/static/03-starboard-operator.config.yaml
Expand Up @@ -35,6 +35,7 @@ data:
vulnerabilityReports.scanner: "Trivy"
configAuditReports.scanner: "Polaris"
kube-bench.imageRef: "docker.io/aquasec/kube-bench:v0.6.6"
compliance.failEntriesLimit: "10"
---
apiVersion: v1
kind: ConfigMap
Expand Down
1 change: 1 addition & 0 deletions deploy/static/starboard.yaml
Expand Up @@ -809,6 +809,7 @@ data:
vulnerabilityReports.scanner: "Trivy"
configAuditReports.scanner: "Polaris"
kube-bench.imageRef: "docker.io/aquasec/kube-bench:v0.6.6"
compliance.failEntriesLimit: "10"
---
apiVersion: v1
kind: ConfigMap
Expand Down
1 change: 1 addition & 0 deletions docs/settings.md
Expand Up @@ -63,6 +63,7 @@ configuration settings for common use cases. For example, switch Trivy from [Sta
| `kube-bench.imageRef` | `docker.io/aquasec/kube-bench:v0.6.6` | kube-bench image reference |
| `kube-hunter.imageRef` | `docker.io/aquasec/kube-hunter:0.6.5` | kube-hunter image reference |
| `kube-hunter.quick` | `"false"` | Whether to use kube-hunter's "quick" scanning mode (subnet 24). Set to `"true"` to enable. |
| `compliance.failEntriesLimit` | `"10"` | Limit the number of fail entries per control check in the cluster compliance detail report. |

!!! tip
You can find it handy to delete a configuration key, which was not created by default by the `starboard install`
Expand Down
16 changes: 13 additions & 3 deletions pkg/cmd/get_clustercompliancereport.go
Expand Up @@ -3,11 +3,14 @@ package cmd
import (
"context"
"fmt"
"io"

"k8s.io/client-go/kubernetes"

"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/starboard/pkg/compliance"
"github.com/aquasecurity/starboard/pkg/starboard"
"github.com/spf13/cobra"
"io"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/genericclioptions"
Expand Down Expand Up @@ -49,8 +52,15 @@ func NewGetClusterComplianceReportsCmd(executable string, cf *genericclioptions.
if err != nil {
return err
}
// generate compliance and compliance failure detail reports
complianceMgr := compliance.NewMgr(kubeClient, logger)
kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return err
}
starboardConfig, err := starboard.NewConfigManager(kubeClientset, starboard.NamespaceName).Read(ctx)
if err != nil {
return err
}
complianceMgr := compliance.NewMgr(kubeClient, logger, starboardConfig)
err = complianceMgr.GenerateComplianceReport(ctx, report.Spec)
if err != nil {
return fmt.Errorf("failed to generate report: %w", err)
Expand Down
2 changes: 0 additions & 2 deletions pkg/compliance/clustercompliancereport.go
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/starboard/pkg/ext"
"github.com/aquasecurity/starboard/pkg/operator/etc"
"github.com/aquasecurity/starboard/pkg/utils"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -20,7 +19,6 @@ import (

type ClusterComplianceReportReconciler struct {
logr.Logger
etc.Config
client.Client
Mgr
ext.Clock
Expand Down
14 changes: 7 additions & 7 deletions pkg/compliance/clustercompliancereport_test.go
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/starboard/pkg/ext"
"github.com/aquasecurity/starboard/pkg/operator/etc"
"github.com/aquasecurity/starboard/pkg/starboard"
"github.com/google/go-cmp/cmp"
"github.com/onsi/ginkgo"
Expand All @@ -34,11 +33,8 @@ func loadResource(filePath string, resource interface{}) error {
}

var _ = ginkgo.Describe("cluster compliance report", func() {
config := etc.Config{
Namespace: "starboard-operator",
}
logger := log.Log.WithName("operator")

config := getStarboardConfig()
ginkgo.Context("reconcile compliance spec report with cis-bench anc audit-config data and validate compliance reports data and requeue", func() {
var cisBenchList v1alpha1.CISKubeBenchReportList
err := loadResource("./testdata/fixture/cisBenchmarkReportList.json", &cisBenchList)
Expand All @@ -60,7 +56,7 @@ var _ = ginkgo.Describe("cluster compliance report", func() {
).Build()

// create compliance controller
instance := ClusterComplianceReportReconciler{Logger: logger, Config: config, Client: client, Mgr: NewMgr(client, logger), Clock: ext.NewSystemClock()}
instance := ClusterComplianceReportReconciler{Logger: logger, Client: client, Mgr: NewMgr(client, logger, config), Clock: ext.NewSystemClock()}

// trigger compliance report generation
_, err = instance.generateComplianceReport(context.TODO(), types.NamespacedName{Namespace: "", Name: "nsa"})
Expand Down Expand Up @@ -151,7 +147,7 @@ var _ = ginkgo.Describe("cluster compliance report", func() {
// create new client
clientWithComplianceSpecOnly := fake.NewClientBuilder().WithScheme(starboard.NewScheme()).WithObjects(&clusterComplianceSpec).Build()
// create compliance controller
complianceControllerInstance := ClusterComplianceReportReconciler{Logger: logger, Config: config, Client: clientWithComplianceSpecOnly, Mgr: NewMgr(clientWithComplianceSpecOnly, logger), Clock: ext.NewSystemClock()}
complianceControllerInstance := ClusterComplianceReportReconciler{Logger: logger, Client: clientWithComplianceSpecOnly, Mgr: NewMgr(clientWithComplianceSpecOnly, logger, config), Clock: ext.NewSystemClock()}
reconcileReport, err := complianceControllerInstance.generateComplianceReport(context.TODO(), types.NamespacedName{Namespace: "", Name: "nsa"})
Expect(err).ToNot(HaveOccurred())

Expand Down Expand Up @@ -216,3 +212,7 @@ func getDetailReport(ctx context.Context, namespaceName types.NamespacedName, cl
}
return &report, nil
}

func getStarboardConfig() starboard.ConfigData {
return starboard.ConfigData{"compliance.failEntriesLimit": "1"}
}
17 changes: 12 additions & 5 deletions pkg/compliance/io.go
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"strings"

"github.com/aquasecurity/starboard/pkg/starboard"

"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/starboard/pkg/ext"
"github.com/emirpasic/gods/sets/hashset"
Expand All @@ -23,16 +25,18 @@ type Mgr interface {
GenerateComplianceReport(ctx context.Context, spec v1alpha1.ReportSpec) error
}

func NewMgr(client client.Client, log logr.Logger) Mgr {
func NewMgr(client client.Client, log logr.Logger, config starboard.ConfigData) Mgr {
return &cm{
client: client,
log: log,
config: config,
}
}

type cm struct {
client client.Client
log logr.Logger
config starboard.ConfigData
}

type summaryTotal struct {
Expand Down Expand Up @@ -242,16 +246,19 @@ func (w *cm) createScanCheckResult(results []*ScannerCheckResult) []v1alpha1.Sca
ctta := make([]v1alpha1.ScannerCheckResult, 0)
for _, checkResult := range results {
var ctt v1alpha1.ScannerCheckResult
rds := make([]v1alpha1.ResultDetails, 0)
failedResultEntries := make([]v1alpha1.ResultDetails, 0)
for _, crd := range checkResult.Details {
if len(failedResultEntries) >= w.config.ComplianceFailEntriesLimit() {
continue
}
//control check detail relevant to fail checks only
if crd.Status == v1alpha1.PassStatus || crd.Status == v1alpha1.WarnStatus {
continue
}
rds = append(rds, v1alpha1.ResultDetails{Name: crd.Name, Namespace: crd.Namespace, Msg: crd.Msg, Status: crd.Status})
failedResultEntries = append(failedResultEntries, v1alpha1.ResultDetails{Name: crd.Name, Namespace: crd.Namespace, Msg: crd.Msg, Status: crd.Status})
}
if len(rds) > 0 {
ctt = v1alpha1.ScannerCheckResult{ID: checkResult.ID, ObjectType: checkResult.ObjectType, Remediation: checkResult.Remediation, Details: rds}
if len(failedResultEntries) > 0 {
ctt = v1alpha1.ScannerCheckResult{ID: checkResult.ID, ObjectType: checkResult.ObjectType, Remediation: checkResult.Remediation, Details: failedResultEntries}
ctta = append(ctta, ctt)
}
}
Expand Down
Expand Up @@ -62,12 +62,6 @@
"namespace": "default",
"msg": "Container 'front-end' of Pod 'rss-site' should set 'securityContext.readOnlyRootFilesystem' to true",
"status": "FAIL"
},
{
"name": "pod-rss-site",
"namespace": "default",
"msg": "Container 'rss-reader' of Pod 'rss-site' should set 'securityContext.readOnlyRootFilesystem' to true",
"status": "FAIL"
}
]
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/operator/operator.go
Expand Up @@ -252,9 +252,8 @@ func Start(ctx context.Context, buildInfo starboard.BuildInfo, operatorConfig et
logger := ctrl.Log.WithName("reconciler").WithName("clustercompliancereport")
cc := &compliance.ClusterComplianceReportReconciler{
Logger: logger,
Config: operatorConfig,
Client: mgr.GetClient(),
Mgr: compliance.NewMgr(mgr.GetClient(), logger),
Mgr: compliance.NewMgr(mgr.GetClient(), logger, starboardConfig),
Clock: ext.NewSystemClock(),
}
if err := cc.SetupWithManager(mgr); err != nil {
Expand Down
23 changes: 20 additions & 3 deletions pkg/starboard/config.go
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"

embedded "github.com/aquasecurity/starboard"
Expand Down Expand Up @@ -62,6 +63,7 @@ const (
keyScanJobTolerations = "scanJob.tolerations"
keyScanJobAnnotations = "scanJob.annotations"
keyScanJobPodTemplateLabels = "scanJob.podTemplateLabels"
keyComplianceFailEntriesLimit = "compliance.failEntriesLimit"
)

// ConfigData holds Starboard configuration settings as a set of key-value
Expand All @@ -81,9 +83,10 @@ func GetDefaultConfig() ConfigData {
keyVulnerabilityReportsScanner: "Trivy",
keyConfigAuditReportsScanner: "Polaris",

"kube-bench.imageRef": "docker.io/aquasec/kube-bench:v0.6.6",
"kube-hunter.imageRef": "docker.io/aquasec/kube-hunter:0.6.5",
"kube-hunter.quick": "false",
"kube-bench.imageRef": "docker.io/aquasec/kube-bench:v0.6.6",
"kube-hunter.imageRef": "docker.io/aquasec/kube-hunter:0.6.5",
"kube-hunter.quick": "false",
"compliance.failEntriesLimit": "10",
}
}

Expand Down Expand Up @@ -209,6 +212,20 @@ func GetVersionFromImageRef(imageRef string) (string, error) {
return version, nil
}

func (c ConfigData) ComplianceFailEntriesLimit() int {
const defaultValue = 10
var value string
var ok bool
if value, ok = c[keyComplianceFailEntriesLimit]; !ok {
return defaultValue
}
intVal, err := strconv.Atoi(value)
if err != nil {
return defaultValue
}
return intVal
}

// NewConfigManager constructs a new ConfigManager that is using kubernetes.Interface
// to manage ConfigData backed by the ConfigMap stored in the specified namespace.
func NewConfigManager(client kubernetes.Interface, namespace string) ConfigManager {
Expand Down
26 changes: 26 additions & 0 deletions pkg/starboard/config_test.go
Expand Up @@ -269,6 +269,32 @@ func TestConfigData_GetScanJobPodTemplateLabels(t *testing.T) {
})
}
}
func TestConfigData_GetComplianceFailEntriesLimit(t *testing.T) {
testCases := []struct {
name string
configData starboard.ConfigData
want int
}{
{
name: "Should return compliance fail entries limit default value",
configData: starboard.ConfigData{},
want: 10,
},
{
name: "Should return compliance fail entries limit from config data",
configData: starboard.ConfigData{
"compliance.failEntriesLimit": "15",
},
want: 15,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotLimit := tc.configData.ComplianceFailEntriesLimit()
assert.Equal(t, tc.want, gotLimit)
})
}
}

func TestConfigData_GetKubeBenchImageRef(t *testing.T) {
testCases := []struct {
Expand Down

0 comments on commit cb5f3b3

Please sign in to comment.