Skip to content

Commit

Permalink
INSIGHTS-329 - Convert kyverno plugin to go and fix policy title (#960)
Browse files Browse the repository at this point in the history
* Convert kyverno to go

* Convert kyverno to go

* Fixing issues

* Preparing build

* Preparing build

* Preparing build

* Preparing build

* Added fatal

* Added fatal
  • Loading branch information
jdesouza authored Sep 11, 2024
1 parent 8a9b3e6 commit 1ef8c99
Show file tree
Hide file tree
Showing 9 changed files with 445 additions and 113 deletions.
2 changes: 1 addition & 1 deletion fairwinds-insights.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ images:
- quay.io/fairwinds/fw-kube-bench:0.4.16
- quay.io/fairwinds/kubectl:0.20.5
- quay.io/fairwinds/fw-kubesec:1.4.8
- quay.io/fairwinds/kyverno:0.2.1
- quay.io/fairwinds/kyverno:0.3.0
- quay.io/fairwinds/fw-opa:2.5.0
- quay.io/fairwinds/postgres-partman:16.0.0
- quay.io/fairwinds/prometheus-collector:1.5.1
Expand Down
19 changes: 12 additions & 7 deletions plugins/kyverno/.goreleaser.yml.envsubst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ release:
disable: true
changelog:
skip: true
# This avoids creating .tar.gz archives.
archives:
- format: binary
env:
- CGO_ENABLED=0
# Avoid contaminating workstation Go directories.
- GOMODCACHE={{ .Env.TMPDIR }}/kyverno-go-mod-cache
- GOBIN={{ .Env.TMPDIR }}/kyverno-go-bin
before:
hooks:
- go mod download
builds:
- skip: true
- main: ./cmd/main.go
# goreleaser builds a matrix of the GOOS, GOArch, and GOARM listed below,
# minus those under `ignore`.
goarch:
Expand All @@ -21,19 +32,13 @@ dockers:
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
# These files will be available to a COPY Dockerfile command.
# Note there are additional extra_files repeated below.
extra_files:
- main.sh
- image_templates:
- "quay.io/fairwinds/kyverno:{{ .FullCommit }}-arm64"
use: buildx
goarch: arm64
goos: linux
build_flag_templates:
- "--platform=linux/arm64"
extra_files:
- main.sh
docker_manifests:
# Create DOcker manifests that make multiple architectures available within a tag,
# and provide partial-version tags like 2, and 2.2.
Expand Down
3 changes: 3 additions & 0 deletions plugins/kyverno/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 0.3.0
* Converted kyverno plugin to golang

## 0.2.1
* Bump alpine to 3.20

Expand Down
17 changes: 3 additions & 14 deletions plugins/kyverno/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
FROM alpine:3.20
ARG TARGETARCH
ARG TARGETOS

WORKDIR /insights
RUN apk -U upgrade
RUN apk update && apk upgrade
RUN apk add jq bash curl moreutils

# curl -L -s https://dl.k8s.io/release/stable.txt
ENV kubectlVersion=v1.29.0
RUN curl -LO https://dl.k8s.io/release/$kubectlVersion/bin/${TARGETOS}/${TARGETARCH}/kubectl
RUN chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl

COPY main.sh .
COPY kyverno /usr/local/bin/insights-kyverno

USER 1000
CMD ["/main.sh"]
CMD ["insights-kyverno"]
161 changes: 161 additions & 0 deletions plugins/kyverno/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package main

import (
"context"
"encoding/json"
"os"

"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/restmapper"
ctrl "sigs.k8s.io/controller-runtime"
)

type Client struct {
RestMapper meta.RESTMapper
DynamicInterface dynamic.Interface
}

func main() {
logrus.Info("Starting Kyverno plugin")
client, err := getKubeClient()
if err != nil {
logrus.Fatal("Error getting kube client: ", err)
}
policiesTitleAndDDescription, err := createPoliciesTitleAndDescriptionMap(client)
if err != nil {
logrus.Fatal("Error creating policies title and description map: ", err)
}
policyReports, err := client.ListPolicies(context.Background(), "PolicyReport", client.DynamicInterface, client.RestMapper)
if err != nil {
logrus.Fatal("Error listing policy reports: ", err)
}
clusterPolicyReports, err := client.ListPolicies(context.Background(), "ClusterPolicyReport", client.DynamicInterface, client.RestMapper)
if err != nil {
logrus.Fatal("Error listing cluster policy reports: ", err)
}
policyReportsViolations, err := filterViolations(policyReports, policiesTitleAndDDescription)
if err != nil {
logrus.Fatal("Error filtering violations: ", err)
}
logrus.Info("Policy reports violations found: ", len(policyReportsViolations))
clusterPolicyReportsViolations, err := filterViolations(clusterPolicyReports, policiesTitleAndDDescription)
if err != nil {
logrus.Fatal("Error filtering violations: ", err)
}
logrus.Info("Cluster policy reports violations found: ", len(clusterPolicyReportsViolations))
response := map[string]interface{}{
"policyReports": policyReportsViolations,
"clusterPolicyReports": clusterPolicyReportsViolations,
}
jsonBytes, err := json.Marshal(response)
if err != nil {
logrus.Fatal("Error marshalling response: ", err)
}
logrus.Info("Writing Kyverno plugin output to /output/kyverno.json")
err = os.WriteFile("/output/kyverno.json", jsonBytes, 0644)
if err != nil {
logrus.Fatal("Error writing output file: ", err)
}
logrus.Info("Kyverno plugin finished")
}

func filterViolations(policies []unstructured.Unstructured, policiesTitleAndDDescription map[string]interface{}) ([]map[string]interface{}, error) {
allViolations := []map[string]interface{}{}
for _, p := range policies {
metadata := p.Object["metadata"].(map[string]interface{})
delete(metadata, "managedFields")
results := p.Object["results"].([]interface{})
violations := []map[string]interface{}{}
for _, r := range results {
result := r.(map[string]interface{})
if result["result"].(string) != "fail" && result["result"].(string) != "warn" {
continue
}
if titleAndDescription, ok := policiesTitleAndDDescription[result["policy"].(string)]; ok {
result["policyTitle"] = titleAndDescription.(map[string]interface{})["title"]
result["policyDescription"] = titleAndDescription.(map[string]interface{})["description"]
}
violations = append(violations, result)
}
if len(violations) == 0 {
continue
}
p.Object["results"] = violations
allViolations = append(allViolations, p.Object)
}
return allViolations, nil
}

func createPoliciesTitleAndDescriptionMap(client *Client) (map[string]interface{}, error) {
clusterPoliciesMetadata, err := client.ListPolicies(context.Background(), "ClusterPolicy", client.DynamicInterface, client.RestMapper)
if err != nil {
return nil, err
}
policiesTitleAndDDescription := map[string]interface{}{}
for _, p := range clusterPoliciesMetadata {
metadata := p.Object["metadata"].(map[string]interface{})
if annotations, ok := metadata["annotations"]; ok {
annotationsMap := annotations.(map[string]interface{})
title := ""
description := ""
if annotationsMap["policies.kyverno.io/title"] != nil {
title = annotationsMap["policies.kyverno.io/title"].(string)
}
if annotationsMap["policies.kyverno.io/description"] != nil {
description = annotationsMap["policies.kyverno.io/description"].(string)
}
policiesTitleAndDDescription[p.GetName()] = map[string]interface{}{
"title": title,
"description": description,
}

}
}
return policiesTitleAndDDescription, nil
}

func getKubeClient() (*Client, error) {
config, err := ctrl.GetConfig()
if err != nil {
return nil, err
}
dynamicInterface, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
}
kube, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
groupResources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
return nil, err
}
restMapper := restmapper.NewDiscoveryRESTMapper(groupResources)

client := Client{
restMapper,
dynamicInterface,
}
return &client, nil
}

func (c *Client) ListPolicies(ctx context.Context, resourceType string, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) ([]unstructured.Unstructured, error) {
gvr, err := restMapper.ResourceFor(schema.GroupVersionResource{
Resource: resourceType,
})
if err != nil {
return nil, err
}
list, err := dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
return list.Items, nil
}
70 changes: 70 additions & 0 deletions plugins/kyverno/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
module github.com/fairwindsops/insights-plugins/plugins/kyverno

go 1.22.1

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.3 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-openapi/jsonpointer v0.20.3 // indirect
github.com/go-openapi/jsonreference v0.20.5 // indirect
github.com/go-openapi/swag v0.22.10 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fairwindsops/insights-plugins/plugins/opa v0.0.0-20240909133638-8a9b3e6f456f
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/sirupsen/logrus v1.9.3
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.31.0 // indirect
k8s.io/apimachinery v0.31.0
k8s.io/client-go v0.31.0
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/controller-runtime v0.19.0
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
Loading

0 comments on commit 1ef8c99

Please sign in to comment.