Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions api/v1alpha1/kubescapevalidator_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,49 @@ import (
type KubescapeValidatorSpec struct {
//+kubebuilder:default=kubescape
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
// global ignored CVEs
IgnoredCVEs []IgnoredCVE `json:"ignoredCVEs,omitempty" yaml:"ignoredCVERule,omitempty"`
// +kubebuilder:validation:MaxItems=5
// +kubebuilder:validation:XValidation:message="KubescapeRules must have a unique RuleName",rule="self.all(e, size(self.filter(x, x.name == e.name)) == 1)"
SeverityLimitRules []SeverityLimitRule `json:"kubescapeRules,omitempty" yaml:"kubescapeRules,omitempty"`

FlaggedCVERules []FlaggedCVE `json:"flaggedCVEs,omitempty" yaml:"flaggedCVERule,omitempty"`
}

type SeverityLimitRule struct {
// Global Severity Limit Rule
SeverityLimitRule SeverityLimitRule `json:"severityLimitRule,omitempty" yaml:"severityLimitRule,omitempty"`
SeverityLimits SeverityLimits `json:"severityLimitRule,omitempty" yaml:"severityLimitRule,omitempty"`
// Global Ignore CVEs
IgnoredCVEs []IgnoredCVE `json:"ignoredCVEs,omitempty" yaml:"ignoredCVERule,omitempty"`
// Rule for Flagged CVEs
FlaggedCVERule []FlaggedCVE `json:"flaggedCVERule,omitempty" yaml:"flaggedCVERule,omitempty"`
}

// FlaggedCVE is a flagged CVE rule.
func (r SeverityLimitRule) Name() string {
return "SeverityLimitRule"
}

type IgnoredCVE string
type FlaggedCVE string

// Name returns the formatted name of the flagged CVE.
func (r FlaggedCVE) Name() string {
return fmt.Sprintf("FLAG-%s", string(r))
}

func (r IgnoredCVE) Name() string {
return fmt.Sprintf("IGNORED-%s", string(r))
}

// ResultCount returns the number of validation results expected for an KubescapeValidatorSpec.
func (s KubescapeValidatorSpec) ResultCount() int {
count := 0
if s.SeverityLimitRule != (SeverityLimitRule{}) {
count++
if s.SeverityLimitRules != nil {
count += len(s.SeverityLimitRules)
}
count += len(s.FlaggedCVERule)

return count
}

// SeverityLimitRule verifies that the number of vulnerabilities of each severity level does not
// exceed the specified limit.
type SeverityLimitRule struct {
type SeverityLimits struct {
Critical *int `json:"critical,omitempty"`
High *int `json:"high,omitempty"`
Medium *int `json:"medium,omitempty"`
Expand All @@ -65,9 +80,8 @@ type SeverityLimitRule struct {
Unknown *int `json:"unknown,omitempty"`
}

// Name is the name of all severity limit rules.
func (r SeverityLimitRule) Name() string {
return "SeverityLimitRule"
func (r SeverityLimits) Name() string {
return "SeverityLimits"
}

// KubescapeValidatorStatus defines the observed state of KubescapeValidator
Expand Down
44 changes: 38 additions & 6 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,48 @@ spec:
spec:
description: KubescapeValidatorSpec defines the desired state of KubescapeValidator
properties:
flaggedCVERule:
description: Rule for Flagged CVEs
flaggedCVEs:
items:
description: FlaggedCVE is a flagged CVE rule.
type: string
type: array
ignoredCVEs:
description: global ignored CVEs
items:
type: string
type: array
kubescapeRules:
items:
properties:
ignoredCVEs:
description: Global Ignore CVEs
items:
type: string
type: array
severityLimitRule:
description: Global Severity Limit Rule
properties:
critical:
type: integer
high:
type: integer
low:
type: integer
medium:
type: integer
negligible:
type: integer
unknown:
type: integer
type: object
type: object
maxItems: 5
type: array
x-kubernetes-validations:
- message: KubescapeRules must have a unique RuleName
rule: self.all(e, size(self.filter(x, x.name == e.name)) == 1)
namespace:
default: kubescape
type: string
severityLimitRule:
description: Global Severity Limit Rule
properties:
critical:
type: integer
high:
type: integer
low:
type: integer
medium:
type: integer
negligible:
type: integer
unknown:
type: integer
type: object
type: object
status:
description: KubescapeValidatorStatus defines the observed state of KubescapeValidator
Expand Down
28 changes: 16 additions & 12 deletions internal/controller/kubescapevalidator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,32 @@ func (r *KubescapeValidatorReconciler) Reconcile(ctx context.Context, req ctrl.R

kubescapeService := validators.NewKubescapeService(r.Log, kubescape)

manifests, err := kubescapeService.Manifests()
manifests, err := kubescapeService.Manifests(validator.Spec.Namespace)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 120}, errors.New("no manifests found")
}

// Reconcile Severity Rule
vrr, err := kubescapeService.ReconcileSeverityRule(validator.Spec.SeverityLimitRule, manifests)
if err != nil {
l.Error(err, "failed to reconcile Severity rule")
}
resp.AddResult(vrr, err)

// Reconcile Flagged CVE Rule
for _, rule := range validator.Spec.FlaggedCVERule {
fmt.Println("ahash")
vrr, err := kubescapeService.ReconcileFlaggedCVERule(rule, manifests)
// Reconcile Kubescape Rule
for _, rule := range validator.Spec.SeverityLimitRules {
vrr, err := kubescapeService.ReconcileSeverityRule(rule, validator.Spec.IgnoredCVEs, manifests)
if err != nil {
l.Error(err, "failed to reconcile Severity rule")
}
resp.AddResult(vrr, err)
}

/*
// Reconcile Flagged CVE Rule
for _, rule := range validator.Spec.FlaggedCVERule {
fmt.Println("ahash")
vrr, err := kubescapeService.ReconcileFlaggedCVERule(nn, rule, manifests)
if err != nil {
l.Error(err, "failed to reconcile Severity rule")
}
resp.AddResult(vrr, err)
}
*/

if err := vres.SafeUpdateValidationResult(ctx, p, vr, resp, r.Log); err != nil {
return ctrl.Result{}, err
}
Expand Down
55 changes: 29 additions & 26 deletions internal/validators/kubescape.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package validators
import (
"context"
"fmt"
"slices"

"github.com/go-logr/logr"
kubevuln "github.com/kubescape/kubevuln/repositories"
Expand Down Expand Up @@ -34,28 +35,25 @@ func NewKubescapeService(log logr.Logger, kvAPI *kubevuln.APIServerStore) *Kubes
}
}

// Manifests retrieves vulnerability data.
func (n *KubescapeService) Manifests() ([]kubescapev1.VulnerabilityManifest, error) {
manifestList, err := n.API.StorageClient.VulnerabilityManifests("kubescape").List(context.Background(), metav1.ListOptions{})
func (n *KubescapeService) Manifests(namespace string) ([]kubescapev1.VulnerabilityManifest, error) {
manifestList, err := n.API.StorageClient.VulnerabilityManifests(namespace).List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}

manifests := make([]kubescapev1.VulnerabilityManifest, 0, len(manifestList.Items))
for _, v := range manifestList.Items {
manifest, err := n.API.StorageClient.VulnerabilityManifests("kubescape").Get(context.Background(), v.Name, metav1.GetOptions{})
manifest, err := n.API.StorageClient.VulnerabilityManifests(namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}

manifests = append(manifests, *manifest)
}

return manifests, nil
}

// ReconcileSeverityRule reconciles a severity limit rule.
func (n *KubescapeService) ReconcileSeverityRule(rule validationv1.SeverityLimitRule, manifests []kubescapev1.VulnerabilityManifest) (*types.ValidationRuleResult, error) {
func (n *KubescapeService) ReconcileSeverityRule(rule validationv1.SeverityLimitRule, ignoredCVEs []validationv1.IgnoredCVE, manifests []kubescapev1.VulnerabilityManifest) (*types.ValidationRuleResult, error) {
vr := buildValidationResult(rule, constants.ValidationTypeSeverity)

critical := 0
Expand All @@ -65,7 +63,7 @@ func (n *KubescapeService) ReconcileSeverityRule(rule validationv1.SeverityLimit
unknown := 0
negligible := 0

foundVulns := validationv1.SeverityLimitRule{
foundVulns := validationv1.SeverityLimits{
Critical: &critical,
High: &high,
Medium: &medium,
Expand All @@ -83,6 +81,11 @@ func (n *KubescapeService) ReconcileSeverityRule(rule validationv1.SeverityLimit
continue
}

// ignore global and rule scoped CVE
if slices.Contains(ignoredCVEs, validationv1.IgnoredCVE(match.Vulnerability.ID)) || slices.Contains(rule.IgnoredCVEs, validationv1.IgnoredCVE(match.Vulnerability.ID)) {
continue
}

uniqueCVEs[match.Vulnerability.ID] = true

switch match.Vulnerability.Severity {
Expand All @@ -103,43 +106,43 @@ func (n *KubescapeService) ReconcileSeverityRule(rule validationv1.SeverityLimit
}

var diff int
if rule.Critical != nil && *foundVulns.Critical > *rule.Critical {
if rule.SeverityLimits.Critical != nil && *foundVulns.Critical > *rule.SeverityLimits.Critical {
vr.Condition.Status = v1.ConditionFalse
diff = *foundVulns.Critical - *rule.Critical
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d unique Critical severity vulnerabilities. %d higher then %d limit.", *foundVulns.Critical, diff, *rule.Critical))
diff = *foundVulns.Critical - *rule.SeverityLimits.Critical
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d unique Critical severity vulnerabilities. %d higher then %d limit.", *foundVulns.Critical, diff, *rule.SeverityLimits.Critical))
}

if rule.High != nil && *foundVulns.High > *rule.High {
if rule.SeverityLimits.High != nil && *foundVulns.High > *rule.SeverityLimits.High {
vr.Condition.Status = v1.ConditionFalse
diff = *foundVulns.High - *rule.High
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d High severity vulnerabilities. %d higher then %d limit.", *foundVulns.High, diff, *rule.High))
diff = *foundVulns.High - *rule.SeverityLimits.High
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d High severity vulnerabilities. %d higher then %d limit.", *foundVulns.High, diff, *rule.SeverityLimits.High))
}

if rule.Medium != nil && *foundVulns.Medium > *rule.Medium {
if rule.SeverityLimits.Medium != nil && *foundVulns.Medium > *rule.SeverityLimits.Medium {
vr.Condition.Status = v1.ConditionFalse
diff = *foundVulns.Medium - *rule.Medium
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Medium severity vulnerabilities. %d higher then %d limit.", *foundVulns.Medium, diff, *rule.Medium))
diff = *foundVulns.Medium - *rule.SeverityLimits.Medium
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Medium severity vulnerabilities. %d higher then %d limit.", *foundVulns.Medium, diff, *rule.SeverityLimits.Medium))

}

if rule.Low != nil && *foundVulns.Low > *rule.Low {
if rule.SeverityLimits.Low != nil && *foundVulns.Low > *rule.SeverityLimits.Low {
vr.Condition.Status = v1.ConditionFalse
diff = *foundVulns.Low - *rule.Low
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Low severity vulnerabilities. %d higher then %d limit.", *foundVulns.Low, diff, *rule.Low))
diff = *foundVulns.Low - *rule.SeverityLimits.Low
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Low severity vulnerabilities. %d higher then %d limit.", *foundVulns.Low, diff, *rule.SeverityLimits.Low))

}

if rule.Unknown != nil && *foundVulns.Unknown > *rule.Unknown {
if rule.SeverityLimits.Unknown != nil && *foundVulns.Unknown > *rule.SeverityLimits.Unknown {
vr.Condition.Status = v1.ConditionFalse
diff = *foundVulns.Unknown - *rule.Unknown
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Unknown severity vulnerabilities. %d higher then %d limit.", *foundVulns.Unknown, diff, *rule.Unknown))
diff = *foundVulns.Unknown - *rule.SeverityLimits.Unknown
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Unknown severity vulnerabilities. %d higher then %d limit.", *foundVulns.Unknown, diff, *rule.SeverityLimits.Unknown))

}

if rule.Negligible != nil && *foundVulns.Negligible > *rule.Negligible {
if rule.SeverityLimits.Negligible != nil && *foundVulns.Negligible > *rule.SeverityLimits.Negligible {
vr.Condition.Status = v1.ConditionFalse
diff = *foundVulns.Negligible - *rule.Negligible
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Negligible severity vulnerabilities. %d higher then %d limit.", *foundVulns.Negligible, diff, *rule.Negligible))
diff = *foundVulns.Negligible - *rule.SeverityLimits.Negligible
vr.Condition.Details = append(vr.Condition.Details, fmt.Sprintf("Found %d Negligible severity vulnerabilities. %d higher then %d limit.", *foundVulns.Negligible, diff, *rule.SeverityLimits.Negligible))
}

if vr.Condition.Status == v1.ConditionFalse {
Expand Down
7 changes: 4 additions & 3 deletions internal/validators/kubescape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ func TestKubescapeService_ReconcileSeverityRule(t *testing.T) {
API *kubevuln.APIServerStore
}
type args struct {
rule validationv1.SeverityLimitRule
manifests []kubescapev1.VulnerabilityManifest
rule validationv1.SeverityLimitRule
ignoredCVEs []validationv1.IgnoredCVE
manifests []kubescapev1.VulnerabilityManifest
}
tests := []struct {
name string
Expand All @@ -35,7 +36,7 @@ func TestKubescapeService_ReconcileSeverityRule(t *testing.T) {
Log: tt.fields.Log,
API: tt.fields.API,
}
got, err := n.ReconcileSeverityRule(tt.args.rule, tt.args.manifests)
got, err := n.ReconcileSeverityRule(tt.args.rule, tt.args.ignoredCVEs, tt.args.manifests)
if (err != nil) != tt.wantErr {
t.Errorf("KubescapeService.ReconcileSeverityRule() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down