mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 07:57:07 +00:00
add report logic in command apply
This commit is contained in:
parent
99d27ec353
commit
f798e9cf2d
8 changed files with 343 additions and 547 deletions
|
@ -1,386 +0,0 @@
|
|||
package jobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/constant"
|
||||
dclient "github.com/kyverno/kyverno/pkg/dclient"
|
||||
v1 "k8s.io/api/batch/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
)
|
||||
|
||||
const workQueueName = "policy-violation-controller"
|
||||
const workQueueRetryLimit = 3
|
||||
|
||||
//Job creates policy report
|
||||
type Job struct {
|
||||
dclient *dclient.Client
|
||||
log logr.Logger
|
||||
queue workqueue.RateLimitingInterface
|
||||
dataStore *dataStore
|
||||
configHandler config.Interface
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// JobInfo defines Job Type
|
||||
type JobInfo struct {
|
||||
JobType string
|
||||
JobData string
|
||||
}
|
||||
|
||||
func (i JobInfo) toKey() string {
|
||||
return fmt.Sprintf("kyverno-%v", i.JobType)
|
||||
}
|
||||
|
||||
//NewDataStore returns an instance of data store
|
||||
func newDataStore() *dataStore {
|
||||
ds := dataStore{
|
||||
data: make(map[string]JobInfo),
|
||||
}
|
||||
return &ds
|
||||
}
|
||||
|
||||
type dataStore struct {
|
||||
data map[string]JobInfo
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (ds *dataStore) add(keyHash string, info JobInfo) {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
ds.data[keyHash] = info
|
||||
}
|
||||
|
||||
func (ds *dataStore) lookup(keyHash string) JobInfo {
|
||||
ds.mu.RLock()
|
||||
defer ds.mu.RUnlock()
|
||||
return ds.data[keyHash]
|
||||
}
|
||||
|
||||
func (ds *dataStore) delete(keyHash string) {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
delete(ds.data, keyHash)
|
||||
}
|
||||
|
||||
//JobsInterface provides API to create PVs
|
||||
type JobsInterface interface {
|
||||
Add(infos ...JobInfo)
|
||||
}
|
||||
|
||||
// NewJobsJob returns a new instance of jobs generator
|
||||
func NewJobsJob(dclient *dclient.Client,
|
||||
configHandler config.Interface,
|
||||
log logr.Logger) *Job {
|
||||
gen := Job{
|
||||
dclient: dclient,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName),
|
||||
dataStore: newDataStore(),
|
||||
configHandler: configHandler,
|
||||
log: log,
|
||||
}
|
||||
return &gen
|
||||
}
|
||||
|
||||
func (j *Job) enqueue(info JobInfo) {
|
||||
keyHash := info.toKey()
|
||||
|
||||
j.dataStore.add(keyHash, info)
|
||||
j.queue.Add(keyHash)
|
||||
j.log.V(4).Info("job added to the queue", "keyhash", keyHash)
|
||||
}
|
||||
|
||||
//Add queues a job creation request
|
||||
func (j *Job) Add(infos ...JobInfo) {
|
||||
for _, info := range infos {
|
||||
j.enqueue(info)
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the workers
|
||||
func (j *Job) Run(workers int, stopCh <-chan struct{}) {
|
||||
logger := j.log
|
||||
defer utilruntime.HandleCrash()
|
||||
logger.Info("start")
|
||||
defer logger.Info("shutting down")
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(j.runWorker, constant.PolicyViolationControllerResync, stopCh)
|
||||
}
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (j *Job) runWorker() {
|
||||
for j.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Job) handleErr(err error, key interface{}) {
|
||||
logger := j.log
|
||||
if err == nil {
|
||||
j.queue.Forget(key)
|
||||
return
|
||||
}
|
||||
|
||||
// retires requests if there is error
|
||||
if j.queue.NumRequeues(key) < workQueueRetryLimit {
|
||||
logger.Error(err, "failed to sync queued jobs", "key", key)
|
||||
// Re-enqueue the key rate limited. Based on the rate limiter on the
|
||||
// queue and the re-enqueue history, the key will be processed later again.
|
||||
j.queue.AddRateLimited(key)
|
||||
return
|
||||
}
|
||||
|
||||
j.queue.Forget(key)
|
||||
if keyHash, ok := key.(string); ok {
|
||||
j.dataStore.delete(keyHash)
|
||||
}
|
||||
|
||||
logger.Error(err, "dropping key out of the queue", "key", key)
|
||||
}
|
||||
|
||||
func (j *Job) processNextWorkItem() bool {
|
||||
logger := j.log
|
||||
obj, shutdown := j.queue.Get()
|
||||
if shutdown {
|
||||
return false
|
||||
}
|
||||
defer j.queue.Done(obj)
|
||||
|
||||
var keyHash string
|
||||
var ok bool
|
||||
if keyHash, ok = obj.(string); !ok {
|
||||
j.queue.Forget(obj)
|
||||
logger.Info("incorrect type; expecting type 'string'", "obj", obj)
|
||||
return true
|
||||
}
|
||||
|
||||
// lookup data store
|
||||
info := j.dataStore.lookup(keyHash)
|
||||
err := j.syncHandler(info)
|
||||
j.handleErr(err, keyHash)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (j *Job) syncHandler(info JobInfo) error {
|
||||
defer func() {
|
||||
j.mux.Unlock()
|
||||
}()
|
||||
j.mux.Lock()
|
||||
|
||||
var err error
|
||||
var wg sync.WaitGroup
|
||||
if info.JobType == constant.BackgroundPolicySync {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
err = j.syncKyverno(&wg, constant.All, constant.BackgroundPolicySync, info.JobData)
|
||||
}()
|
||||
}
|
||||
|
||||
if info.JobType == constant.ConfigmapMode {
|
||||
// shuting?
|
||||
if info.JobData == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
scopes := strings.Split(info.JobData, ",")
|
||||
if len(scopes) == 1 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
err = j.syncKyverno(&wg, constant.All, constant.ConfigmapMode, "")
|
||||
}()
|
||||
} else {
|
||||
wg.Add(len(scopes))
|
||||
for _, scope := range scopes {
|
||||
go func(scope string) {
|
||||
err = j.syncKyverno(&wg, scope, constant.ConfigmapMode, "")
|
||||
}(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func (j *Job) syncKyverno(wg *sync.WaitGroup, scope, jobType, data string) error {
|
||||
defer wg.Done()
|
||||
|
||||
go j.cleanupCompletedJobs()
|
||||
|
||||
mode := "cli"
|
||||
args := []string{
|
||||
"report",
|
||||
"all",
|
||||
fmt.Sprintf("--mode=%s", "configmap"),
|
||||
}
|
||||
|
||||
if jobType == constant.BackgroundPolicySync || jobType == constant.BackgroundSync {
|
||||
switch scope {
|
||||
case constant.Namespace:
|
||||
args = []string{
|
||||
"report",
|
||||
"namespace",
|
||||
fmt.Sprintf("--mode=%s", mode),
|
||||
}
|
||||
case constant.Cluster:
|
||||
args = []string{
|
||||
"report",
|
||||
"cluster",
|
||||
fmt.Sprintf("--mode=%s", mode),
|
||||
}
|
||||
case constant.All:
|
||||
args = []string{
|
||||
"report",
|
||||
"all",
|
||||
fmt.Sprintf("--mode=%s", mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if jobType == constant.BackgroundPolicySync && data != "" {
|
||||
args = append(args, fmt.Sprintf("-p=%s", data))
|
||||
}
|
||||
|
||||
exbackoff := &backoff.ExponentialBackOff{
|
||||
InitialInterval: backoff.DefaultInitialInterval,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: time.Second,
|
||||
MaxElapsedTime: 5 * time.Minute,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
exbackoff.Reset()
|
||||
err := backoff.Retry(func() error {
|
||||
resourceList, err := j.dclient.ListResource("", "Job", config.KubePolicyNamespace, &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"scope": scope,
|
||||
"type": jobType,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list jobs: %v", err)
|
||||
}
|
||||
|
||||
if len(resourceList.Items) != 0 {
|
||||
return fmt.Errorf("found %d Jobs", len(resourceList.Items))
|
||||
}
|
||||
return nil
|
||||
}, exbackoff)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return j.CreateJob(args, jobType, scope)
|
||||
}
|
||||
|
||||
// CreateJob will create Job template for background scan
|
||||
func (j *Job) CreateJob(args []string, jobType, scope string) error {
|
||||
ttl := new(int32)
|
||||
*ttl = 60
|
||||
|
||||
job := &v1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: config.KubePolicyNamespace,
|
||||
Labels: map[string]string{
|
||||
"scope": scope,
|
||||
"type": jobType,
|
||||
},
|
||||
},
|
||||
Spec: v1.JobSpec{
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
Spec: apiv1.PodSpec{
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
Name: strings.ToLower(fmt.Sprintf("%s-%s", jobType, scope)),
|
||||
Image: config.KyvernoCliImage,
|
||||
ImagePullPolicy: apiv1.PullAlways,
|
||||
Args: args,
|
||||
},
|
||||
},
|
||||
ServiceAccountName: "kyverno-service-account",
|
||||
RestartPolicy: "OnFailure",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
job.Spec.TTLSecondsAfterFinished = ttl
|
||||
job.SetGenerateName("kyverno-policyreport-")
|
||||
if _, err := j.dclient.CreateResource("", "Job", config.KubePolicyNamespace, job, false); err != nil {
|
||||
return fmt.Errorf("failed to create job: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *Job) cleanupCompletedJobs() {
|
||||
logger := j.log.WithName("cleanup jobs")
|
||||
|
||||
exbackoff := &backoff.ExponentialBackOff{
|
||||
InitialInterval: backoff.DefaultInitialInterval,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: time.Second,
|
||||
MaxElapsedTime: 2 * time.Minute,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
exbackoff.Reset()
|
||||
err := backoff.Retry(func() error {
|
||||
resourceList, err := j.dclient.ListResource("", "Job", config.KubePolicyNamespace, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list jobs : %v", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list pods : %v", err)
|
||||
}
|
||||
|
||||
for _, job := range resourceList.Items {
|
||||
succeeded, ok, _ := unstructured.NestedInt64(job.Object, "status", "succeeded")
|
||||
if ok && succeeded > 0 {
|
||||
if errnew := j.dclient.DeleteResource("", "Job", job.GetNamespace(), job.GetName(), false); errnew != nil {
|
||||
err = errnew
|
||||
continue
|
||||
}
|
||||
|
||||
podList, errNew := j.dclient.ListResource("", "Pod", config.KubePolicyNamespace, &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"job-name": job.GetName(),
|
||||
},
|
||||
})
|
||||
|
||||
if errNew != nil {
|
||||
err = errNew
|
||||
continue
|
||||
}
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
if errpod := j.dclient.DeleteResource("", "Pod", pod.GetNamespace(), pod.GetName(), false); errpod != nil {
|
||||
logger.Error(errpod, "failed to delete pod", "name", pod.GetName())
|
||||
err = errpod
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}, exbackoff)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to clean up completed jobs")
|
||||
}
|
||||
}
|
|
@ -3,45 +3,28 @@ package apply
|
|||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
yamlv2 "gopkg.in/yaml.v2"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
log "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
|
@ -187,13 +170,13 @@ func Command() *cobra.Command {
|
|||
}
|
||||
|
||||
yamlBytes := []byte(resourceStr)
|
||||
resources, err = getResource(yamlBytes)
|
||||
resources, err = common.GetResource(yamlBytes)
|
||||
if err != nil {
|
||||
return sanitizedError.NewWithError("failed to extract the resources", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resources, err = getResources(policies, resourcePaths, dClient)
|
||||
resources, err = common.GetResources(policies, resourcePaths, dClient)
|
||||
if err != nil {
|
||||
return sanitizedError.NewWithError("failed to load resources", err)
|
||||
}
|
||||
|
@ -277,135 +260,6 @@ func Command() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func getResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient *client.Client) ([]*unstructured.Unstructured, error) {
|
||||
var resources []*unstructured.Unstructured
|
||||
var err error
|
||||
|
||||
if dClient != nil {
|
||||
var resourceTypesMap = make(map[string]bool)
|
||||
var resourceTypes []string
|
||||
for _, policy := range policies {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
resourceTypesMap[kind] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for kind := range resourceTypesMap {
|
||||
resourceTypes = append(resourceTypes, kind)
|
||||
}
|
||||
|
||||
resources, err = getResourcesOfTypeFromCluster(resourceTypes, dClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, resourcePath := range resourcePaths {
|
||||
resourceBytes, err := getFileBytes(resourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getResources, err := getResource(resourceBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, resource := range getResources {
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func getResourcesOfTypeFromCluster(resourceTypes []string, dClient *client.Client) ([]*unstructured.Unstructured, error) {
|
||||
var resources []*unstructured.Unstructured
|
||||
|
||||
for _, kind := range resourceTypes {
|
||||
resourceList, err := dClient.ListResource("", kind, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := resourceList.GetAPIVersion()
|
||||
for _, resource := range resourceList.Items {
|
||||
resource.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: version,
|
||||
Kind: kind,
|
||||
})
|
||||
resources = append(resources, resource.DeepCopy())
|
||||
}
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func getFileBytes(path string) ([]byte, error) {
|
||||
file, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, err
|
||||
}
|
||||
|
||||
func getResource(resourceBytes []byte) ([]*unstructured.Unstructured, error) {
|
||||
resources := make([]*unstructured.Unstructured, 0)
|
||||
var getErrString string
|
||||
|
||||
files, splitDocError := utils.SplitYAMLDocuments(resourceBytes)
|
||||
if splitDocError != nil {
|
||||
return nil, splitDocError
|
||||
}
|
||||
|
||||
for _, resourceYaml := range files {
|
||||
resource, err := convertResourceToUnstructured(resourceYaml)
|
||||
if err != nil {
|
||||
getErrString = getErrString + err.Error() + "\n"
|
||||
}
|
||||
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
|
||||
if getErrString != "" {
|
||||
return nil, errors.New(getErrString)
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func convertResourceToUnstructured(resourceYaml []byte) (*unstructured.Unstructured, error) {
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
resourceObject, metaData, err := decode(resourceYaml, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&resourceObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceJSON, err := json.Marshal(resourceUnstructured)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource, err := engineutils.ConvertToUnstructured(resourceJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource.SetGroupVersionKind(*metaData)
|
||||
|
||||
if resource.GetNamespace() == "" {
|
||||
resource.SetNamespace("default")
|
||||
}
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// applyPolicyOnResource - function to apply policy on resource
|
||||
func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured, mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, rc *resultCounts) error {
|
||||
responseError := false
|
||||
|
|
156
pkg/kyverno/apply/report.go
Normal file
156
pkg/kyverno/apply/report.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package apply
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
policyreportv1alpha1 "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
log "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
const clusterpolicyreport = "clusterpolicyreport"
|
||||
|
||||
// resps is the engine reponses generated for a single policy
|
||||
func buildPolicyReports(resps []response.EngineResponse) (res []*unstructured.Unstructured) {
|
||||
var raw []byte
|
||||
var err error
|
||||
|
||||
resultsMap := buildPolicyResults(resps)
|
||||
for scope, result := range resultsMap {
|
||||
if scope == clusterpolicyreport {
|
||||
report := &policyreportv1alpha1.ClusterPolicyReport{
|
||||
Results: result,
|
||||
Summary: calculateSummary(result),
|
||||
}
|
||||
|
||||
report.SetName(scope)
|
||||
if raw, err = json.Marshal(report); err != nil {
|
||||
log.Log.Error(err, "failed to serilize policy report", "name", report.Name, "scope", scope)
|
||||
}
|
||||
} else {
|
||||
report := &policyreportv1alpha1.PolicyReport{
|
||||
Results: result,
|
||||
Summary: calculateSummary(result),
|
||||
}
|
||||
|
||||
ns := strings.ReplaceAll(scope, "policyreport-ns-", "")
|
||||
report.SetName(scope)
|
||||
report.SetNamespace(ns)
|
||||
}
|
||||
|
||||
reportUnstructured, err := engineutils.ConvertToUnstructured(raw)
|
||||
if err != nil {
|
||||
log.Log.Error(err, "failed to convert policy report", "scope", scope)
|
||||
continue
|
||||
}
|
||||
res = append(res, reportUnstructured)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// buildPolicyResults returns a string-PolicyReportResult map
|
||||
// the key of the map is one of "clusterpolicyreport", "policyreport-ns-<namespace>"
|
||||
func buildPolicyResults(resps []response.EngineResponse) map[string][]*policyreportv1alpha1.PolicyReportResult {
|
||||
results := make(map[string][]*policyreportv1alpha1.PolicyReportResult)
|
||||
infos := policyreport.GeneratePRsFromEngineResponse(resps, log.Log)
|
||||
|
||||
for _, info := range infos {
|
||||
var appname string
|
||||
|
||||
ns := info.Resource.GetNamespace()
|
||||
if ns != "" {
|
||||
appname = fmt.Sprintf("policyreport-ns-%s", ns)
|
||||
} else {
|
||||
appname = fmt.Sprintf(clusterpolicyreport)
|
||||
}
|
||||
|
||||
result := &policyreportv1alpha1.PolicyReportResult{
|
||||
Policy: info.PolicyName,
|
||||
Resources: []*corev1.ObjectReference{
|
||||
{
|
||||
Kind: info.Resource.GetKind(),
|
||||
Namespace: info.Resource.GetNamespace(),
|
||||
APIVersion: info.Resource.GetAPIVersion(),
|
||||
Name: info.Resource.GetName(),
|
||||
UID: info.Resource.GetUID(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range info.Rules {
|
||||
result.Rule = rule.Name
|
||||
result.Message = rule.Message
|
||||
result.Status = policyreportv1alpha1.PolicyStatus(rule.Check)
|
||||
results[appname] = append(results[appname], result)
|
||||
}
|
||||
}
|
||||
|
||||
return mergeSucceededResults(results)
|
||||
}
|
||||
|
||||
func mergeSucceededResults(results map[string][]*policyreportv1alpha1.PolicyReportResult) map[string][]*policyreportv1alpha1.PolicyReportResult {
|
||||
resultsNew := make(map[string][]*policyreportv1alpha1.PolicyReportResult)
|
||||
|
||||
for scope, scopedResults := range results {
|
||||
|
||||
resourcesMap := make(map[string]*policyreportv1alpha1.PolicyReportResult)
|
||||
for _, result := range scopedResults {
|
||||
if result.Status != policyreportv1alpha1.PolicyStatus("Pass") {
|
||||
resultsNew[scope] = append(resultsNew[scope], result)
|
||||
continue
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s/%s", result.Policy, result.Rule)
|
||||
if r, ok := resourcesMap[key]; !ok {
|
||||
resourcesMap[key] = &policyreportv1alpha1.PolicyReportResult{}
|
||||
resourcesMap[key] = result
|
||||
} else {
|
||||
r.Resources = append(r.Resources, result.Resources...)
|
||||
resourcesMap[key] = r
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range resourcesMap {
|
||||
names := strings.Split(k, "/")
|
||||
if len(names) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
r := &policyreportv1alpha1.PolicyReportResult{
|
||||
Policy: names[0],
|
||||
Rule: names[1],
|
||||
Resources: v.Resources,
|
||||
Status: policyreportv1alpha1.PolicyStatus("Pass"),
|
||||
Scored: true,
|
||||
}
|
||||
|
||||
resultsNew[scope] = append(resultsNew[scope], r)
|
||||
}
|
||||
}
|
||||
return resultsNew
|
||||
}
|
||||
|
||||
func calculateSummary(results []*policyreportv1alpha1.PolicyReportResult) (summary policyreportv1alpha1.PolicyReportSummary) {
|
||||
for _, res := range results {
|
||||
switch string(res.Status) {
|
||||
case "Pass":
|
||||
summary.Pass++
|
||||
case "Fail":
|
||||
summary.Fail++
|
||||
case "Warn":
|
||||
summary.Warn++
|
||||
case "Error":
|
||||
summary.Error++
|
||||
case "Skip":
|
||||
summary.Skip++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
166
pkg/kyverno/common/fetch.go
Normal file
166
pkg/kyverno/common/fetch.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
|
||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme"
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GetResources gets matched resources by the given policies
|
||||
// the resources are fetched from
|
||||
// - local paths to resources, if given
|
||||
// - the k8s cluster, if given
|
||||
func GetResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient *client.Client) ([]*unstructured.Unstructured, error) {
|
||||
var resources []*unstructured.Unstructured
|
||||
var err error
|
||||
|
||||
if dClient != nil {
|
||||
var resourceTypesMap = make(map[string]bool)
|
||||
var resourceTypes []string
|
||||
for _, policy := range policies {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
for _, kind := range rule.MatchResources.Kinds {
|
||||
resourceTypesMap[kind] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for kind := range resourceTypesMap {
|
||||
resourceTypes = append(resourceTypes, kind)
|
||||
}
|
||||
|
||||
resources, err = getResourcesOfTypeFromCluster(resourceTypes, dClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, resourcePath := range resourcePaths {
|
||||
resourceBytes, err := getFileBytes(resourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getResources, err := GetResource(resourceBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, resource := range getResources {
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// GetResource converts raw bytes to unstructured object
|
||||
func GetResource(resourceBytes []byte) ([]*unstructured.Unstructured, error) {
|
||||
resources := make([]*unstructured.Unstructured, 0)
|
||||
var getErrString string
|
||||
|
||||
files, splitDocError := utils.SplitYAMLDocuments(resourceBytes)
|
||||
if splitDocError != nil {
|
||||
return nil, splitDocError
|
||||
}
|
||||
|
||||
for _, resourceYaml := range files {
|
||||
resource, err := convertResourceToUnstructured(resourceYaml)
|
||||
if err != nil {
|
||||
getErrString = getErrString + err.Error() + "\n"
|
||||
}
|
||||
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
|
||||
if getErrString != "" {
|
||||
return nil, errors.New(getErrString)
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// shuting?
|
||||
// // GetPoliciesFromCluster fetches the policies from the cluster
|
||||
// func GetPoliciesFromCluster(pNames []string, dClient *client.Client) ([]*v1.ClusterPolicy, error) {
|
||||
// resourceTyeps := []string{"ClusterPolicy", "Policy"}
|
||||
// policies, err := getResourcesOfTypeFromCluster(resourceTyeps, dClient)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // if its a namespace policy, fill in namespaces in match / exclude? when converting to cluster policy
|
||||
// var cpols []*v1.ClusterPolicy
|
||||
// for _, p := range policies {
|
||||
// cpols = append(cpols, policy.ConvertPolicyToClusterPolicy(p))
|
||||
// }
|
||||
// }
|
||||
|
||||
func getResourcesOfTypeFromCluster(resourceTypes []string, dClient *client.Client) ([]*unstructured.Unstructured, error) {
|
||||
var resources []*unstructured.Unstructured
|
||||
|
||||
for _, kind := range resourceTypes {
|
||||
resourceList, err := dClient.ListResource("", kind, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := resourceList.GetAPIVersion()
|
||||
for _, resource := range resourceList.Items {
|
||||
resource.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: version,
|
||||
Kind: kind,
|
||||
})
|
||||
resources = append(resources, resource.DeepCopy())
|
||||
}
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func getFileBytes(path string) ([]byte, error) {
|
||||
file, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, err
|
||||
}
|
||||
|
||||
func convertResourceToUnstructured(resourceYaml []byte) (*unstructured.Unstructured, error) {
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
resourceObject, metaData, err := decode(resourceYaml, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&resourceObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceJSON, err := json.Marshal(resourceUnstructured)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource, err := engineutils.ConvertToUnstructured(resourceJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource.SetGroupVersionKind(*metaData)
|
||||
|
||||
if resource.GetNamespace() == "" {
|
||||
resource.SetNamespace("default")
|
||||
}
|
||||
return resource, nil
|
||||
}
|
|
@ -19,8 +19,8 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/constant"
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/event"
|
||||
"github.com/kyverno/kyverno/pkg/jobs"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport/jobs"
|
||||
"github.com/kyverno/kyverno/pkg/policyviolation"
|
||||
"github.com/kyverno/kyverno/pkg/resourcecache"
|
||||
"github.com/kyverno/kyverno/pkg/webhookconfig"
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/constant"
|
||||
dclient "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/jobs"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport/jobs"
|
||||
"github.com/kyverno/kyverno/pkg/policystatus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
|
|
@ -2,9 +2,11 @@ package policyviolation
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/common"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
)
|
||||
|
||||
|
@ -16,10 +18,14 @@ func GeneratePVsFromEngineResponse(ers []response.EngineResponse, log logr.Logge
|
|||
log.V(4).Info("resource does no have a name assigned yet, not creating a policy violation", "resource", er.PolicyResponse.Resource)
|
||||
continue
|
||||
}
|
||||
|
||||
// skip when response succeed
|
||||
if er.IsSuccessful() {
|
||||
continue
|
||||
if os.Getenv("POLICY-TYPE") != common.PolicyReport {
|
||||
if er.IsSuccessful() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// build policy violation info
|
||||
pvInfos = append(pvInfos, buildPVInfo(er))
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/common"
|
||||
"github.com/kyverno/kyverno/pkg/constant"
|
||||
dclient "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/jobs"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport"
|
||||
"github.com/kyverno/kyverno/pkg/policyreport/jobs"
|
||||
"github.com/kyverno/kyverno/pkg/policystatus"
|
||||
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
|
Loading…
Add table
Reference in a new issue