1
0
Fork 0
mirror of https://github.com/kyverno/policy-reporter.git synced 2024-12-14 11:57:32 +00:00

PolicyReport Filter for Namespaces

Signed-off-by: Frank Jogeleit <frank.jogeleit@web.de>
This commit is contained in:
Frank Jogeleit 2022-03-20 21:19:42 +01:00
parent 91291042fe
commit 74c18f4dcd
13 changed files with 216 additions and 13 deletions

View file

@ -1,5 +1,11 @@
# Changelog
# 2.7.0
* Policy Reporter
* PolicyReport Filter:
* PolicyReporter CRD Filter by Namespaces
* Disable ClusterPolicyReport CRD processing
# 2.6.3
* Policy Reporter
* Fix Debouncer has wrong reference to OldPolicyReport when a result was cached.

View file

@ -5,8 +5,8 @@ description: |
It creates Prometheus Metrics and can send rule validation events to different targets like Loki, Elasticsearch, Slack or Discord
type: application
version: 2.6.3
appVersion: 2.3.2
version: 2.7.0
appVersion: 2.4.0
icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png
home: https://kyverno.github.io/policy-reporter

View file

@ -75,4 +75,17 @@ s3:
{{- with .Values.policyPriorities }}
priorityMap:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}
reportFilter:
namespaces:
{{- with .Values.reportFilter.namespaces.include }}
include:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.reportFilter.namespaces.exclude }}
exclude:
{{- toYaml . | nindent 6 }}
{{- end }}
clusterReports:
disabled: {{ .Values.reportFilter.clusterReports.disabled }}

View file

@ -2,7 +2,7 @@ image:
registry: ghcr.io
repository: kyverno/policy-reporter
pullPolicy: IfNotPresent
tag: 2.3.2
tag: 2.4.0
imagePullSecrets: []
@ -97,6 +97,18 @@ rest:
metrics:
enabled: false
# Filter PolicyReport resources to process
reportFilter:
namespaces:
# Process only PolicyReport resources from an included namespace, wildcards are supported
include: []
# Ignore all PolicyReport resources from a excluded namespace, wildcards are supported
# exclude will be ignored if an include filter exists
exclude: []
clusterReports:
# Enable the processing of ClusterPolicyReports, enabled by default
enabled: true
# enable policy-report-ui
ui:
enabled: false

1
go.mod
View file

@ -35,6 +35,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/minio/pkg v1.1.20 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect

2
go.sum
View file

@ -341,6 +341,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/minio/pkg v1.1.20 h1:NhYtoQHw/Sl1ib/lroANFwkQspE0YyTeVR1CMPEff/A=
github.com/minio/pkg v1.1.20/go.mod h1:Xo7LQshlxGa9shKwJ7NzQbgW4s8T/Wc1cOStR/eUiMY=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=

View file

@ -80,6 +80,20 @@ type Metrics struct {
type PriorityMap = map[string]string
type NamespaceFilter struct {
Include []string `mapstructure:"include"`
Exclude []string `mapstructure:"exclude"`
}
type ClusterReportFilter struct {
Disabled bool `mapstructure:"disabled"`
}
type ReportFilter struct {
Namespaces NamespaceFilter `mapstructure:"namespaces"`
ClusterReports ClusterReportFilter `mapstructure:"clusterReports"`
}
// Config of the PolicyReporter
type Config struct {
Loki Loki `mapstructure:"loki"`
@ -95,4 +109,5 @@ type Config struct {
Metrics Metrics `mapstructure:"metrics"`
REST REST `mapstructure:"rest"`
PriorityMap PriorityMap `mapstructure:"priorityMap"`
ReportFilter ReportFilter `mapstructure:"reportFilter"`
}

View file

@ -369,11 +369,19 @@ func (r *Resolver) PolicyReportClient() (report.PolicyReportClient, error) {
return nil, err
}
r.policyReportClient = kubernetes.NewPolicyReportClient(client, r.Mapper(), 5*time.Second)
r.policyReportClient = kubernetes.NewPolicyReportClient(client, r.Mapper(), 5*time.Second, r.ReportFilter())
return r.policyReportClient, nil
}
func (r *Resolver) ReportFilter() report.Filter {
return report.NewFilter(
r.config.ReportFilter.ClusterReports.Disabled,
r.config.ReportFilter.Namespaces.Include,
r.config.ReportFilter.Namespaces.Exclude,
)
}
// ResultCache resolver method
func (r *Resolver) ResultCache() *cache.Cache {
if r.resultCache != nil {

View file

@ -363,6 +363,15 @@ func Test_ResolveMapper(t *testing.T) {
}
}
func Test_ResolveReportFilter(t *testing.T) {
resolver := config.NewResolver(testConfig, &rest.Config{})
filter := resolver.ReportFilter()
if filter == nil {
t.Error("Error: Should return Filter")
}
}
func Test_ResolveClientWithInvalidK8sConfig(t *testing.T) {
k8sConfig := &rest.Config{}
k8sConfig.Host = "invalid/url"

View file

@ -46,6 +46,7 @@ type k8sPolicyReportClient struct {
mapper Mapper
mx *sync.Mutex
restartWatchOnFailure time.Duration
reportFilter report.Filter
}
func (k *k8sPolicyReportClient) GetFoundResources() map[string]string {
@ -53,6 +54,8 @@ func (k *k8sPolicyReportClient) GetFoundResources() map[string]string {
}
func (k *k8sPolicyReportClient) WatchPolicyReports(ctx context.Context) <-chan report.LifecycleEvent {
schemas := [][]schema.GroupVersionResource{}
pr := []schema.GroupVersionResource{
policyReportAlphaV2,
policyReportAlphaV1,
@ -63,7 +66,12 @@ func (k *k8sPolicyReportClient) WatchPolicyReports(ctx context.Context) <-chan r
clusterPolicyReportAlphaV1,
}
for _, versions := range [][]schema.GroupVersionResource{pr, cpor} {
schemas = append(schemas, pr)
if !k.reportFilter.DisableClusterReports() {
schemas = append(schemas, cpor)
}
for _, versions := range schemas {
go func(vs []schema.GroupVersionResource) {
for {
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(k.client, 30*time.Minute, corev1.NamespaceAll, nil)
@ -78,7 +86,9 @@ func (k *k8sPolicyReportClient) WatchPolicyReports(ctx context.Context) <-chan r
}
for {
if len(k.found) == 2 {
if !k.reportFilter.DisableClusterReports() && len(k.found) == 2 {
break
} else if k.reportFilter.DisableClusterReports() && len(k.found) == 1 {
break
}
}
@ -106,13 +116,17 @@ func (k *k8sPolicyReportClient) watchCRD(ctx context.Context, r schema.GroupVers
AddFunc: func(obj interface{}) {
if item, ok := obj.(*unstructured.Unstructured); ok {
preport := k.mapper.MapPolicyReport(item.Object)
k.debouncer.Add(report.LifecycleEvent{NewPolicyReport: preport, OldPolicyReport: &report.PolicyReport{}, Type: report.Added})
if k.reportFilter.AllowReport(preport) {
k.debouncer.Add(report.LifecycleEvent{NewPolicyReport: preport, OldPolicyReport: &report.PolicyReport{}, Type: report.Added})
}
}
},
DeleteFunc: func(obj interface{}) {
if item, ok := obj.(*unstructured.Unstructured); ok {
preport := k.mapper.MapPolicyReport(item.Object)
k.debouncer.Add(report.LifecycleEvent{NewPolicyReport: preport, OldPolicyReport: &report.PolicyReport{}, Type: report.Deleted})
if k.reportFilter.AllowReport(preport) {
k.debouncer.Add(report.LifecycleEvent{NewPolicyReport: preport, OldPolicyReport: &report.PolicyReport{}, Type: report.Deleted})
}
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
@ -124,7 +138,9 @@ func (k *k8sPolicyReportClient) watchCRD(ctx context.Context, r schema.GroupVers
oreport = k.mapper.MapPolicyReport(oldItem.Object)
}
k.debouncer.Add(report.LifecycleEvent{NewPolicyReport: preport, OldPolicyReport: oreport, Type: report.Updated})
if k.reportFilter.AllowReport(preport) {
k.debouncer.Add(report.LifecycleEvent{NewPolicyReport: preport, OldPolicyReport: oreport, Type: report.Updated})
}
}
},
})
@ -153,7 +169,7 @@ func (k *k8sPolicyReportClient) handleCRDRegistration(ctx context.Context, infor
}
// NewPolicyReportAdapter new Adapter for Policy Report Kubernetes API
func NewPolicyReportClient(dynamic dynamic.Interface, mapper Mapper, restartWatchOnFailure time.Duration) report.PolicyReportClient {
func NewPolicyReportClient(dynamic dynamic.Interface, mapper Mapper, restartWatchOnFailure time.Duration, reportFilter report.Filter) report.PolicyReportClient {
return &k8sPolicyReportClient{
client: dynamic,
mapper: mapper,
@ -161,5 +177,6 @@ func NewPolicyReportClient(dynamic dynamic.Interface, mapper Mapper, restartWatc
found: make(map[string]string),
debouncer: NewDebouncer(time.Minute),
restartWatchOnFailure: restartWatchOnFailure,
reportFilter: reportFilter,
}
}

View file

@ -7,15 +7,18 @@ import (
"time"
"github.com/kyverno/policy-reporter/pkg/kubernetes"
"github.com/kyverno/policy-reporter/pkg/report"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var filter = report.NewFilter(false, make([]string, 0, 0), make([]string, 0, 0))
func Test_PolicyWatcher(t *testing.T) {
ctx := context.Background()
kclient, rclient := NewFakeCilent()
client := kubernetes.NewPolicyReportClient(kclient, NewMapper(), 100*time.Millisecond)
client := kubernetes.NewPolicyReportClient(kclient, NewMapper(), 100*time.Millisecond, filter)
eventChan := client.WatchPolicyReports(ctx)
@ -48,7 +51,7 @@ func Test_GetFoundResources(t *testing.T) {
ctx := context.Background()
kclient, _ := NewFakeCilent()
client := kubernetes.NewPolicyReportClient(kclient, NewMapper(), 100*time.Millisecond)
client := kubernetes.NewPolicyReportClient(kclient, NewMapper(), 100*time.Millisecond, filter)
client.WatchPolicyReports(ctx)
@ -58,3 +61,18 @@ func Test_GetFoundResources(t *testing.T) {
t.Errorf("Should find PolicyReport and ClusterPolicyReport Resource")
}
}
func Test_GetFoundResourcesWihDisabledClusterReports(t *testing.T) {
ctx := context.Background()
kclient, _ := NewFakeCilent()
client := kubernetes.NewPolicyReportClient(kclient, NewMapper(), 100*time.Millisecond, report.NewFilter(true, make([]string, 0, 0), make([]string, 0, 0)))
client.WatchPolicyReports(ctx)
time.Sleep(1 * time.Second)
if len(client.GetFoundResources()) != 1 {
t.Errorf("Should find only PolicyReport Resource")
}
}

44
pkg/report/filter.go Normal file
View file

@ -0,0 +1,44 @@
package report
import "github.com/minio/pkg/wildcard"
type Filter interface {
DisableClusterReports() bool
AllowReport(report *PolicyReport) bool
}
type filter struct {
disbaleClusterReports bool
includeNamespaces []string
excludeNamespaces []string
}
func (f *filter) DisableClusterReports() bool {
return f.disbaleClusterReports
}
func (f *filter) AllowReport(report *PolicyReport) bool {
if report.Namespace == "" {
return true
} else if len(f.includeNamespaces) > 0 {
for _, ns := range f.includeNamespaces {
if wildcard.Match(ns, report.Namespace) {
return true
}
}
return false
} else if len(f.excludeNamespaces) > 0 {
for _, ns := range f.excludeNamespaces {
if wildcard.Match(ns, report.Namespace) {
return false
}
}
}
return true
}
func NewFilter(disableClusterReports bool, includeNamespaces []string, excludeNamespaces []string) Filter {
return &filter{disableClusterReports, includeNamespaces, excludeNamespaces}
}

58
pkg/report/filter_test.go Normal file
View file

@ -0,0 +1,58 @@
package report_test
import (
"testing"
"github.com/kyverno/policy-reporter/pkg/report"
)
func Test_DisableClusterReports(t *testing.T) {
filter := report.NewFilter(true, make([]string, 0), make([]string, 0))
if !filter.DisableClusterReports() {
t.Error("Expected EnableClusterReports to return true as configured")
}
}
func Test_AllowReport(t *testing.T) {
t.Run("Allow ClusterReport", func(t *testing.T) {
filter := report.NewFilter(true, make([]string, 0), []string{"*"})
if !filter.AllowReport(creport) {
t.Error("Expected AllowReport returns true if Report is a ClusterPolicyReport without namespace")
}
})
t.Run("Allow Report with matching include Namespace", func(t *testing.T) {
filter := report.NewFilter(true, []string{"patch", "te*"}, []string{})
if !filter.AllowReport(preport) {
t.Error("Expected AllowReport returns true if Report namespace matches include pattern")
}
})
t.Run("Disallow Report with matching exclude Namespace", func(t *testing.T) {
filter := report.NewFilter(true, []string{}, []string{"patch", "te*"})
if filter.AllowReport(preport) {
t.Error("Expected AllowReport returns false if Report namespace matches exclude pattern")
}
})
t.Run("Ignores exclude pattern if include namespaces provided", func(t *testing.T) {
filter := report.NewFilter(true, []string{"*"}, []string{"te*"})
if !filter.AllowReport(preport) {
t.Error("Expected AllowReport returns true because exclude patterns ignored if include patterns provided")
}
})
t.Run("Allow Report when no configuration exists", func(t *testing.T) {
filter := report.NewFilter(true, []string{}, []string{})
if !filter.AllowReport(preport) {
t.Error("Expected AllowReport returns true if no namespace patterns configured")
}
})
t.Run("Disallow Report if no include namespace matches", func(t *testing.T) {
filter := report.NewFilter(true, []string{"patch", "dev"}, []string{})
if filter.AllowReport(preport) {
t.Error("Expected AllowReport returns false if no namespace pattern matches")
}
})
}