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:
parent
91291042fe
commit
74c18f4dcd
13 changed files with 216 additions and 13 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }}
|
|
@ -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
1
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
44
pkg/report/filter.go
Normal 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
58
pkg/report/filter_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue