diff --git a/go.mod b/go.mod index eb62f6b7b8..ec4c896f1f 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/google/generative-ai-go v0.11.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/hupe1980/go-huggingface v0.0.15 + github.com/kyverno/policy-reporter-kyverno-plugin v1.6.3 github.com/olekukonko/tablewriter v0.0.5 github.com/oracle/oci-go-sdk/v65 v65.65.1 github.com/prometheus/prometheus v0.53.1 @@ -98,6 +99,7 @@ require ( github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/segmentio/fasthash v1.0.3 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect diff --git a/go.sum b/go.sum index 743b6fd503..8b2f2aa0cc 100644 --- a/go.sum +++ b/go.sum @@ -2006,6 +2006,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyverno/policy-reporter-kyverno-plugin v1.6.3 h1:wr6Hglqa9Lw31nMnypXGJb+ujK3vmxacC/lJMZkp5No= +github.com/kyverno/policy-reporter-kyverno-plugin v1.6.3/go.mod h1:ms0YASmim7qtJresYsGuhkpCtQLWQIEYFA9ftOu4oQc= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -2221,6 +2223,8 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 h1:yGAraK1uUjlhSXgNMIy8o/J4LF github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks= github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4= +github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= diff --git a/pkg/ai/prompts.go b/pkg/ai/prompts.go index e0ce72cf8d..e9cd480d1d 100644 --- a/pkg/ai/prompts.go +++ b/pkg/ai/prompts.go @@ -48,6 +48,16 @@ const ( - Containers: - {list of container names} ` + + kyverno_prompt = `Simplify the following Kyverno warnings message delimited by triple dashes written in --- %s --- language; --- %s ---. + Provide the most probable solution as a kubectl command. + + Write the output in the following format, for the solution, only show the kubectl command: + + Error: {Explain error here} + + Solution: {kubectl command} + ` ) var PromptMap = map[string]string{ @@ -56,4 +66,6 @@ var PromptMap = map[string]string{ "ConfigAuditReport": trivy_conf_prompt, "PrometheusConfigValidate": prom_conf_prompt, "PrometheusConfigRelabelReport": prom_relabel_prompt, + "PolicyReport": kyverno_prompt, + "ClusterPolicyReport": kyverno_prompt, } diff --git a/pkg/common/types.go b/pkg/common/types.go index b0f82da339..4e9d10cac6 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -21,6 +21,7 @@ import ( "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" keda "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + kyverno "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2" regv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" autov1 "k8s.io/api/autoscaling/v1" @@ -63,9 +64,11 @@ type PreAnalysis struct { Gateway gtwapi.Gateway HTTPRoute gtwapi.HTTPRoute // Integrations - ScaledObject keda.ScaledObject - TrivyVulnerabilityReport trivy.VulnerabilityReport - TrivyConfigAuditReport trivy.ConfigAuditReport + ScaledObject keda.ScaledObject + TrivyVulnerabilityReport trivy.VulnerabilityReport + TrivyConfigAuditReport trivy.ConfigAuditReport + KyvernoPolicyReport kyverno.PolicyReport + KyvernoClusterPolicyReport kyverno.ClusterPolicyReport } type Result struct { diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index d93b4d34d1..c3ad08cf05 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/k8sgpt-ai/k8sgpt/pkg/integration/aws" + "github.com/k8sgpt-ai/k8sgpt/pkg/integration/kyverno" "github.com/k8sgpt-ai/k8sgpt/pkg/common" "github.com/k8sgpt-ai/k8sgpt/pkg/integration/keda" @@ -52,6 +53,7 @@ var integrations = map[string]IIntegration{ "prometheus": prometheus.NewPrometheus(), "aws": aws.NewAWS(), "keda": keda.NewKeda(), + "kyverno": kyverno.NewKyverno(), } func NewIntegration() *Integration { diff --git a/pkg/integration/kyverno/analyzer.go b/pkg/integration/kyverno/analyzer.go new file mode 100644 index 0000000000..0a20e82e2e --- /dev/null +++ b/pkg/integration/kyverno/analyzer.go @@ -0,0 +1,162 @@ +/* +Copyright 2023 The K8sGPT Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kyverno + +import ( + "fmt" + + ctrl "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/k8sgpt-ai/k8sgpt/pkg/common" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" + + "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2" +) + +// "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2" + +type KyvernoAnalyzer struct { + policyReportAnalysis bool + clusterReportAnalysis bool +} + +func (KyvernoAnalyzer) analyzePolicyReports(a common.Analyzer) ([]common.Result, error) { + result := &v1alpha2.PolicyReportList{} + client := a.Client.CtrlClient + + err := v1alpha2.AddToScheme(client.Scheme()) + if err != nil { + return nil, err + } + if err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil { + return nil, err + } + + // Find criticals and get CVE + var preAnalysis = map[string]common.PreAnalysis{} + + for _, report := range result.Items { + + // For each pod there may be multiple vulnerabilities + var failures []common.Failure + for _, vuln := range report.Results { + if vuln.Result == "fail" { + // get the vulnerability ID + // get the vulnerability description + failures = append(failures, common.Failure{ + Text: fmt.Sprintf("policy failure: %s (message: %s)", vuln.Policy, vuln.Message), + Sensitive: []common.Sensitive{}, + }) + } + } + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", report.Namespace, + report.Name)] = common.PreAnalysis{ + KyvernoPolicyReport: report, + FailureDetails: failures, + } + } + } + + for key, value := range preAnalysis { + var currentAnalysis = common.Result{ + Kind: "PolicyReport", + Name: key, + Error: value.FailureDetails, + } + + parent, _ := util.GetParent(a.Client, value.KyvernoPolicyReport.ObjectMeta) + currentAnalysis.ParentObject = parent + a.Results = append(a.Results, currentAnalysis) + } + + return a.Results, nil + +} + +func (t KyvernoAnalyzer) analyzeClusterPolicyReports(a common.Analyzer) ([]common.Result, error) { + result := &v1alpha2.ClusterPolicyReportList{} + client := a.Client.CtrlClient + + err := v1alpha2.AddToScheme(client.Scheme()) + if err != nil { + return nil, err + } + if err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil { + return nil, err + } + + // Find criticals and get CVE + var preAnalysis = map[string]common.PreAnalysis{} + + for _, report := range result.Items { + + // For each pod there may be multiple vulnerabilities + var failures []common.Failure + for _, vuln := range report.Results { + if vuln.Severity == "CRITICAL" { + // get the vulnerability ID + // get the vulnerability description + failures = append(failures, common.Failure{ + Text: fmt.Sprintf("critical Vulnerability found ID: %s (learn more at: %s)", vuln.ID, vuln.Source), + Sensitive: []common.Sensitive{}, + }) + } + } + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", report.Namespace, + report.Name)] = common.PreAnalysis{ + KyvernoClusterPolicyReport: report, + FailureDetails: failures, + } + } + } + + for key, value := range preAnalysis { + var currentAnalysis = common.Result{ + Kind: "ClusterPolicyReport", + Name: key, + Error: value.FailureDetails, + } + + parent, _ := util.GetParent(a.Client, value.KyvernoClusterPolicyReport.ObjectMeta) + currentAnalysis.ParentObject = parent + a.Results = append(a.Results, currentAnalysis) + } + + return a.Results, nil +} + +func (t KyvernoAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { + + if t.policyReportAnalysis { + common := make([]common.Result, 0) + vresult, err := t.analyzePolicyReports(a) + if err != nil { + return nil, err + } + common = append(common, vresult...) + return common, nil + } + if t.clusterReportAnalysis { + common := make([]common.Result, 0) + cresult, err := t.analyzeClusterPolicyReports(a) + if err != nil { + return nil, err + } + common = append(common, cresult...) + return common, nil + } + return make([]common.Result, 0), nil +} diff --git a/pkg/integration/kyverno/kyverno.go b/pkg/integration/kyverno/kyverno.go new file mode 100644 index 0000000000..ae1af7a973 --- /dev/null +++ b/pkg/integration/kyverno/kyverno.go @@ -0,0 +1,117 @@ +/* +Copyright 2023 The K8sGPT Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kyverno + +import ( + "os" + + "github.com/fatih/color" + "github.com/k8sgpt-ai/k8sgpt/pkg/common" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" + "github.com/spf13/viper" +) + +type Kyverno struct{} + +func NewKyverno() *Kyverno { + return &Kyverno{} +} + +func (k *Kyverno) GetAnalyzerName() []string { + return []string{ + //from wgpolicyk8s.io/v1alpha2 + "PolicyReport", + "ClusterPolicyReport", + } +} + +func (k *Kyverno) OwnsAnalyzer(analyzer string) bool { + + for _, a := range k.GetAnalyzerName() { + if analyzer == a { + return true + } + } + return false +} + +func (k *Kyverno) isDeployed() bool { + // check if wgpolicyk8s apigroup is available as a marker if new policy resource available is installed on the cluster + kubecontext := viper.GetString("kubecontext") + kubeconfig := viper.GetString("kubeconfig") + client, err := kubernetes.NewClient(kubecontext, kubeconfig) + if err != nil { + // TODO: better error handling + color.Red("Error initialising kubernetes client: %v", err) + os.Exit(1) + } + groups, _, err := client.Client.Discovery().ServerGroupsAndResources() + if err != nil { + // TODO: better error handling + color.Red("Error initialising discovery client: %v", err) + os.Exit(1) + } + + for _, group := range groups { + if group.Name == "kyverno.io" { + return true + } + } + + return false +} + +func (k *Kyverno) isFilterActive() bool { + activeFilters := viper.GetStringSlice("active_filters") + + for _, filter := range k.GetAnalyzerName() { + for _, af := range activeFilters { + if af == filter { + return true + } + } + } + + return false +} + +func (k *Kyverno) IsActivate() bool { + if k.isFilterActive() && k.isDeployed() { + return true + } else { + return false + } +} + +func (k *Kyverno) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) { + + (*mergedMap)["PolicyReport"] = &KyvernoAnalyzer{ + policyReportAnalysis: true, + } + (*mergedMap)["ClusterPolicyReport"] = &KyvernoAnalyzer{ + clusterReportAnalysis: true, + } +} + +func (k *Kyverno) Deploy(namespace string) error { + return nil +} + +func (k *Kyverno) UnDeploy(_ string) error { + return nil +} + +func (t *Kyverno) GetNamespace() (string, error) { + return "", nil +}