Skip to content

Commit

Permalink
Development (#7)
Browse files Browse the repository at this point in the history
* Implement elasticsearch
* Update deployment
* Add Changelog
  • Loading branch information
fjogeleit authored Feb 27, 2021
1 parent eab3ff0 commit 2872a25
Show file tree
Hide file tree
Showing 20 changed files with 769 additions and 127 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## 0.7.0

* Implement Elasticsearch as Target for PolicyReportResults
* Replace CLI flags with a single `config.yaml` to manage target-configurations as separate `ConfigMap`
* Set `loki.skipExistingOnStartup` default value to `true`
45 changes: 40 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,64 @@

## Motivation

Kyverno ships with two types of validation. You can either enforce a rule or audit it. If you don't want to block developers or if you want to try out a new rule, you can use the audit functionality. The audit configuration creates [PolicyReports](https://kyverno.io/docs/policy-reports/) which you can access with `kubectl`. Because I can't find a simple solution to get a general overview of this PolicyReports and PolicyReportResults, I created this tool to send information from PolicyReports to [Grafana Loki](https://grafana.com/oss/loki/). As additional feature this tool provides an http server with Prometheus Metrics about ReportPolicy Summaries and ReportPolicyRules.
Kyverno ships with two types of validation. You can either enforce a rule or audit it. If you don't want to block developers or if you want to try out a new rule, you can use the audit functionality. The audit configuration creates [PolicyReports](https://kyverno.io/docs/policy-reports/) which you can access with `kubectl`. Because I can't find a simple solution to get a general overview of this PolicyReports and PolicyReportResults, I created this tool to send information from PolicyReports to different targets like [Grafana Loki](https://grafana.com/oss/loki/). This tool provides by default an HTTP server with Prometheus Metrics on `http://localhost:2112/metrics` about ReportPolicy Summaries and ReportPolicyRules.

This project is in an early stage. Please let me know if anything did not work as expected or if you want to send your audits to other targets then Loki.

## Installation with Helm v3

Installation via Helm Repository

### Add the Helm repository

```bash
helm repo add policy-reporter https://fjogeleit.github.io/policy-reporter
helm install policy-reporter policy-reporter/policy-reporter --set loki.host=http://lokihost:3100 -n policy-reporter --create-namespace
```

### Basic Installation - Provides Prometheus Metrics

```bash
helm install policy-reporter policy-reporter/policy-reporter -n policy-reporter --create-namespace
```

### Installation with Loki

```bash
helm install policy-reporter policy-reporter/policy-reporter --set loki.host=http://loki:3100 -n policy-reporter --create-namespace
```

### Installation with Elasticsearch

```bash
helm install policy-reporter policy-reporter/policy-reporter --set elasticsearch.host=http://elasticsearch:3100 -n policy-reporter --create-namespace
```

You can also customize the `./charts/policy-reporter/values.yaml` to change the default configurations.

### Additional configurations for Loki

Configure `loki.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
Configure `loki.skipExistingOnStartup` to skip all results who already existed before the PolicyReporter started. Can be used after the first deployment to prevent duplicated events.
* Configure `loki.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
* Configure `loki.skipExistingOnStartup` to skip all results who already existed before the PolicyReporter started (default: `true`).

```yaml
loki:
minimumPriority: ""
skipExistingOnStartup: false
skipExistingOnStartup: true
```
### Additional configurations for Elasticsearch
* Configure `elasticsearch.index` to customize the elasticsearch index.
* Configure `elasticsearch.rotation` is added as suffix to the index. Possible values are `daily`, `monthly`, `annually` and `none`.
* Configure `elasticsearch.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
* Configure `elasticsearch.skipExistingOnStartup` to skip all results who already existed before the PolicyReporter started (default: `true`).

```yaml
elasticsearch:
index: "policy-reporter"
rotation: "daily"
minimumPriority: ""
skipExistingOnStartup: true
```

### Configure Policy Priorities
Expand Down
4 changes: 2 additions & 2 deletions charts/policy-reporter/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ name: policy-reporter
description: K8s PolicyReporter watches for wgpolicyk8s.io/v1alpha1.PolicyReport resources. It creates Prometheus Metrics and can send rule validation events to Loki

type: application
version: 0.7.2
appVersion: 0.6.0
version: 0.8.0
appVersion: 0.7.0
10 changes: 2 additions & 8 deletions charts/policy-reporter/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ spec:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --loki={{ .Values.loki.host }}
{{- if .Values.loki.minimumPriority }}
- --loki-minimum-priority={{ .Values.loki.minimumPriority }}
{{- end }}
{{- if .Values.loki.skipExistingOnStartup }}
- --loki-skip-existing-on-startup
{{- end }}
- --config=/app/config.yaml
ports:
- name: http
containerPort: 2112
Expand All @@ -56,5 +50,5 @@ spec:
volumes:
- name: config-volume
configMap:
name: policy-reporter-config
name: {{ include "policyreporter.fullname" . }}-targets
optional: true
19 changes: 19 additions & 0 deletions charts/policy-reporter/templates/targetsconfigmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "policyreporter.fullname" . }}-targets
labels:
{{- include "policyreporter.labels" . | nindent 4 }}
data:
config.yaml: |-
loki:
host: {{ .Values.loki.host | quote }}
minimumPriority: {{ .Values.loki.minimumPriority | quote }}
skipExistingOnStartup: {{ .Values.loki.skipExistingOnStartup }}
elasticsearch:
host: {{ .Values.elasticsearch.host | quote }}
index: {{ .Values.elasticsearch.index | default "policy-reporter" | quote }}
rotation: {{ .Values.elasticsearch.rotation | default "dayli" | quote }}
minimumPriority: {{ .Values.elasticsearch.minimumPriority | quote }}
skipExistingOnStartup: {{ .Values.elasticsearch.skipExistingOnStartup }}
20 changes: 17 additions & 3 deletions charts/policy-reporter/values.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
loki:
# loki host address
host: http://loki.loki-stack.svc.cluster.local:3100
host: ""
# minimum priority "" < info < warning < error
minimumPriority: ""
# Skip already existing PolicyReportResults on startup
skipExistingOnStartup: false
skipExistingOnStartup: true

elasticsearch:
# elasticsearch host address
host: ""
# elasticsearch index (default: policy-reporter)
index: ""
# elasticsearch index rotation and index suffix
# possible values: dayli, monthly, annually, none (default: dayli)
rotation: ""
# minimum priority "" < info < warning < error
minimumPriority: ""
# Skip already existing PolicyReportResults on startup
skipExistingOnStartup: true


metrics:
serviceMonitor: false
Expand All @@ -15,7 +29,7 @@ metrics:
image:
repository: fjogeleit/policy-reporter
pullPolicy: IfNotPresent
tag: 0.6.0
tag: 0.7.0

imagePullSecrets: []

Expand Down
87 changes: 25 additions & 62 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,79 +1,24 @@
package cmd

import (
"flag"
"net/http"
"log"

"github.com/fjogeleit/policy-reporter/pkg/config"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
)

// NewCLI creates a new instance of the root CLI
func NewCLI() *cobra.Command {
rootCmd := &cobra.Command{
Use: "run",
Short: "Kyverno Policy API",
Long: `Kyverno Policy API and Monitoring`,
RunE: func(cmd *cobra.Command, args []string) error {
c, err := loadConfig(cmd)
if err != nil {
return err
}

resolver := config.NewResolver(c)

client, err := resolver.PolicyReportClient()
if err != nil {
return err
}

policyMetrics, err := resolver.PolicyReportMetrics()
if err != nil {
return err
}

clusterPolicyMetrics, err := resolver.ClusterPolicyReportMetrics()
if err != nil {
return err
}

loki := resolver.LokiClient()

g := new(errgroup.Group)

g.Go(policyMetrics.GenerateMetrics)

g.Go(clusterPolicyMetrics.GenerateMetrics)

if loki != nil {
g.Go(func() error {
return client.WatchRuleValidation(func(r report.Result) {
go loki.Send(r)
}, c.Loki.SkipExisting)
})
}

g.Go(func() error {
http.Handle("/metrics", promhttp.Handler())

return http.ListenAndServe(":2112", nil)
})

return g.Wait()
},
Use: "policyreporter",
Short: "Generates PolicyReport Metrics and Send Results to different targets",
Long: `Generates Prometheus Metrics from PolicyReports, ClusterPolicyReports and PolicyReportResults.
Sends notifications to different targets like Grafana's Loki.`,
}

rootCmd.PersistentFlags().StringP("kubeconfig", "k", "", "absolute path to the kubeconfig file")

rootCmd.PersistentFlags().String("loki", "", "loki host: http://loki:3100")
rootCmd.PersistentFlags().String("loki-minimum-priority", "", "Minimum Priority to send Results to Loki (info < warning < error)")
rootCmd.PersistentFlags().Bool("loki-skip-existing-on-startup", false, "Skip Results created before PolicyReporter started. Prevent duplicated sending after new deployment")

flag.Parse()
rootCmd.AddCommand(newRunCMD())
rootCmd.AddCommand(newSendCMD())

return rootCmd
}
Expand All @@ -83,8 +28,26 @@ func loadConfig(cmd *cobra.Command) (*config.Config, error) {

v.SetDefault("namespace", "policy-reporter")

cfgFile := ""

configFlag := cmd.Flags().Lookup("config")
if configFlag != nil {
cfgFile = configFlag.Value.String()
}

if cfgFile != "" {
v.SetConfigFile(cfgFile)
} else {
v.AddConfigPath(".")
v.SetConfigName("config")
}

v.AutomaticEnv()

if err := v.ReadInConfig(); err != nil {
log.Println("[INFO] No target configuration file found")
}

if flag := cmd.Flags().Lookup("loki"); flag != nil {
v.BindPFlag("loki.host", flag)
}
Expand Down
89 changes: 89 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cmd

import (
"flag"
"net/http"

"github.com/fjogeleit/policy-reporter/pkg/config"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)

func newRunCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Short: "Run PolicyReporter Watcher & HTTP Metrics Server",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := loadConfig(cmd)
if err != nil {
return err
}

resolver := config.NewResolver(c)

client, err := resolver.PolicyReportClient()
if err != nil {
return err
}

policyMetrics, err := resolver.PolicyReportMetrics()
if err != nil {
return err
}

clusterPolicyMetrics, err := resolver.ClusterPolicyReportMetrics()
if err != nil {
return err
}

g := new(errgroup.Group)

g.Go(policyMetrics.GenerateMetrics)

g.Go(clusterPolicyMetrics.GenerateMetrics)

g.Go(func() error {
targets := resolver.TargetClients()

if len(targets) == 0 {
return nil
}

return client.WatchPolicyReportResults(func(r report.Result, e bool) {
for _, t := range targets {
go func(target target.Client, result report.Result, preExisted bool) {
if preExisted && target.SkipExistingOnStartup() {
return
}

target.Send(result)
}(t, r, e)
}
}, resolver.SkipExistingOnStartup())
})

g.Go(func() error {
http.Handle("/metrics", promhttp.Handler())

return http.ListenAndServe(":2112", nil)
})

return g.Wait()
},
}

// For local usage
cmd.PersistentFlags().StringP("kubeconfig", "k", "", "absolute path to the kubeconfig file")
cmd.PersistentFlags().StringP("config", "c", "", "target configuration file")

cmd.PersistentFlags().String("loki", "", "loki host: http://loki:3100")
cmd.PersistentFlags().String("loki-minimum-priority", "", "Minimum Priority to send Results to Loki (info < warning < error)")
cmd.PersistentFlags().Bool("loki-skip-existing-on-startup", false, "Skip Results created before PolicyReporter started. Prevent duplicated sending after new deployment")

flag.Parse()

return cmd
}
Loading

0 comments on commit 2872a25

Please sign in to comment.