1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 02:18:15 +00:00

feat: generate backgroundscan reports for validating admission policies (#8135)

* feat: generate backgroundscan reports for validating admission policies

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: skip validate check images if errors are encourted when validating the resource

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2023-09-05 14:42:17 +03:00 committed by GitHub
parent c0a74fe0d5
commit 8732183cc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 320 additions and 138 deletions

View file

@ -294,6 +294,7 @@ The chart values are organised per component.
| features.admissionReports.enabled | bool | `true` | Enables the feature |
| features.aggregateReports.enabled | bool | `true` | Enables the feature |
| features.policyReports.enabled | bool | `true` | Enables the feature |
| features.validatingAdmissionPolicyReports.enabled | bool | `false` | Enables the feature |
| features.autoUpdateWebhooks.enabled | bool | `true` | Enables the feature |
| features.backgroundScan.enabled | bool | `true` | Enables the feature |
| features.backgroundScan.backgroundScanWorkers | int | `2` | Number of background scan workers |

View file

@ -39,4 +39,8 @@ The following components have been installed in your cluster:
{{- with .Values.features.generateValidatingAdmissionPolicy.enabled }}
⚠️ WARNING: Generating validating admission policy requires a Kubernetes 1.26+ cluster with `ValidatingAdmissionPolicy` feature gate and `admissionregistration.k8s.io` API group enabled.
{{- end }}
{{- end }}
{{- with .Values.features.validatingAdmissionPolicyReports.enabled }}
⚠️ WARNING: Validating admission policy reports require a Kubernetes 1.26+ cluster with `ValidatingAdmissionPolicy` feature gate and `admissionregistration.k8s.io` API group enabled.
{{- end }}

View file

@ -19,6 +19,9 @@
{{- with .policyReports -}}
{{- $flags = append $flags (print "--policyReports=" .enabled) -}}
{{- end -}}
{{- with .validatingAdmissionPolicyReports -}}
{{- $flags = append $flags (print "--validatingAdmissionPolicyReports=" .enabled) -}}
{{- end -}}
{{- with .autoUpdateWebhooks -}}
{{- $flags = append $flags (print "--autoUpdateWebhooks=" .enabled) -}}
{{- end -}}

View file

@ -112,6 +112,7 @@ spec:
"admissionReports"
"aggregateReports"
"policyReports"
"validatingAdmissionPolicyReports"
"backgroundScan"
"configMapCaching"
"deferredLoading"

View file

@ -381,6 +381,9 @@ features:
policyReports:
# -- Enables the feature
enabled: true
validatingAdmissionPolicyReports:
# -- Enables the feature
enabled: false
autoUpdateWebhooks:
# -- Enables the feature
enabled: true

View file

@ -23,7 +23,9 @@ import (
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/leaderelection"
"github.com/kyverno/kyverno/pkg/logging"
"k8s.io/apimachinery/pkg/runtime/schema"
kubeinformers "k8s.io/client-go/informers"
admissionregistrationv1alpha1informers "k8s.io/client-go/informers/admissionregistration/v1alpha1"
metadatainformers "k8s.io/client-go/metadata/metadatainformer"
kyamlopenapi "sigs.k8s.io/kustomize/kyaml/openapi"
)
@ -38,6 +40,7 @@ func createReportControllers(
admissionReports bool,
aggregateReports bool,
policyReports bool,
validatingAdmissionPolicyReports bool,
reportsChunkSize int,
backgroundScanWorkers int,
client dclient.Interface,
@ -52,12 +55,19 @@ func createReportControllers(
) ([]internal.Controller, func(context.Context) error) {
var ctrls []internal.Controller
var warmups []func(context.Context) error
var vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer
// check if validating admission policies are registered in the API server
if validatingAdmissionPolicyReports {
vapInformer = kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies()
}
kyvernoV1 := kyvernoInformer.Kyverno().V1()
if backgroundScan || admissionReports {
resourceReportController := resourcereportcontroller.NewController(
client,
kyvernoV1.Policies(),
kyvernoV1.ClusterPolicies(),
vapInformer,
)
warmups = append(warmups, func(ctx context.Context) error {
return resourceReportController.Warmup(ctx)
@ -93,25 +103,27 @@ func createReportControllers(
))
}
if backgroundScan {
backgroundScanController := backgroundscancontroller.NewController(
client,
kyvernoClient,
eng,
metadataFactory,
kyvernoV1.Policies(),
kyvernoV1.ClusterPolicies(),
vapInformer,
kubeInformer.Core().V1().Namespaces(),
resourceReportController,
backgroundScanInterval,
configuration,
jp,
eventGenerator,
policyReports,
)
ctrls = append(ctrls, internal.NewController(
backgroundscancontroller.ControllerName,
backgroundscancontroller.NewController(
client,
kyvernoClient,
eng,
metadataFactory,
kyvernoV1.Policies(),
kyvernoV1.ClusterPolicies(),
kubeInformer.Core().V1().Namespaces(),
resourceReportController,
backgroundScanInterval,
configuration,
jp,
eventGenerator,
policyReports,
),
backgroundScanWorkers,
))
backgroundScanController,
backgroundScanWorkers),
)
}
}
return ctrls, func(ctx context.Context) error {
@ -130,6 +142,7 @@ func createrLeaderControllers(
admissionReports bool,
aggregateReports bool,
policyReports bool,
validatingAdmissionPolicyReports bool,
reportsChunkSize int,
backgroundScanWorkers int,
kubeInformer kubeinformers.SharedInformerFactory,
@ -148,6 +161,7 @@ func createrLeaderControllers(
admissionReports,
aggregateReports,
policyReports,
validatingAdmissionPolicyReports,
reportsChunkSize,
backgroundScanWorkers,
dynamicClient,
@ -165,22 +179,24 @@ func createrLeaderControllers(
func main() {
var (
backgroundScan bool
admissionReports bool
aggregateReports bool
policyReports bool
reportsChunkSize int
backgroundScanWorkers int
backgroundScanInterval time.Duration
maxQueuedEvents int
omitEvents string
skipResourceFilters bool
backgroundScan bool
admissionReports bool
aggregateReports bool
policyReports bool
validatingAdmissionPolicyReports bool
reportsChunkSize int
backgroundScanWorkers int
backgroundScanInterval time.Duration
maxQueuedEvents int
omitEvents string
skipResourceFilters bool
)
flagset := flag.NewFlagSet("reports-controller", flag.ExitOnError)
flagset.BoolVar(&backgroundScan, "backgroundScan", true, "Enable or disable background scan.")
flagset.BoolVar(&admissionReports, "admissionReports", true, "Enable or disable admission reports.")
flagset.BoolVar(&aggregateReports, "aggregateReports", true, "Enable or disable aggregated policy reports.")
flagset.BoolVar(&policyReports, "policyReports", true, "Enable or disable policy reports.")
flagset.BoolVar(&validatingAdmissionPolicyReports, "validatingAdmissionPolicyReports", false, "Enable or disable validating admission policy reports.")
flagset.IntVar(&reportsChunkSize, "reportsChunkSize", 1000, "Max number of results in generated reports, reports will be split accordingly if there are more results to be stored.")
flagset.IntVar(&backgroundScanWorkers, "backgroundScanWorkers", backgroundscancontroller.Workers, "Configure the number of background scan workers.")
flagset.DurationVar(&backgroundScanInterval, "backgroundScanInterval", time.Hour, "Configure background scan interval.")
@ -219,6 +235,14 @@ func main() {
// ELSE KYAML IS NOT THREAD SAFE
kyamlopenapi.Schema()
setup.Logger.Info("background scan interval", "duration", backgroundScanInterval.String())
// check if validating admission policies are registered in the API server
if validatingAdmissionPolicyReports {
groupVersion := schema.GroupVersion{Group: "admissionregistration.k8s.io", Version: "v1alpha1"}
if _, err := setup.KyvernoDynamicClient.GetKubeClient().Discovery().ServerResourcesForGroupVersion(groupVersion.String()); err != nil {
setup.Logger.Error(err, "validating admission policies aren't supported.")
os.Exit(1)
}
}
// informer factories
kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(setup.KyvernoClient, resyncPeriod)
omitEventsValues := strings.Split(omitEvents, ",")
@ -277,6 +301,7 @@ func main() {
admissionReports,
aggregateReports,
policyReports,
validatingAdmissionPolicyReports,
reportsChunkSize,
backgroundScanWorkers,
kubeInformer,

View file

@ -42208,6 +42208,7 @@ spec:
- --admissionReports=true
- --aggregateReports=true
- --policyReports=true
- --validatingAdmissionPolicyReports=false
- --backgroundScan=true
- --backgroundScanWorkers=2
- --backgroundScanInterval=1h

View file

@ -15,6 +15,7 @@ import (
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/controllers"
"github.com/kyverno/kyverno/pkg/controllers/report/resource"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
@ -226,7 +227,7 @@ func (c *controller) reconcileReport(ctx context.Context, policyMap map[string]p
for _, result := range results {
policy := policyMap[result.Policy]
if policy.policy != nil {
reportutils.SetPolicyLabel(report, policy.policy)
reportutils.SetPolicyLabel(report, engineapi.NewKyvernoPolicy(policy.policy))
}
}
return reportutils.CreateReport(ctx, report, c.client)
@ -238,7 +239,7 @@ func (c *controller) reconcileReport(ctx context.Context, policyMap map[string]p
for _, result := range results {
policy := policyMap[result.Policy]
if policy.policy != nil {
reportutils.SetPolicyLabel(after, policy.policy)
reportutils.SetPolicyLabel(after, engineapi.NewKyvernoPolicy(policy.policy))
}
}
reportutils.SetResults(after, results...)

View file

@ -22,11 +22,14 @@ import (
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
admissionregistrationv1alpha1informers "k8s.io/client-go/informers/admissionregistration/v1alpha1"
corev1informers "k8s.io/client-go/informers/core/v1"
admissionregistrationv1alpha1listers "k8s.io/client-go/listers/admissionregistration/v1alpha1"
corev1listers "k8s.io/client-go/listers/core/v1"
metadatainformers "k8s.io/client-go/metadata/metadatainformer"
"k8s.io/client-go/tools/cache"
@ -51,6 +54,7 @@ type controller struct {
// listers
polLister kyvernov1listers.PolicyLister
cpolLister kyvernov1listers.ClusterPolicyLister
vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister
bgscanrLister cache.GenericLister
cbgscanrLister cache.GenericLister
nsLister corev1listers.NamespaceLister
@ -76,6 +80,7 @@ func NewController(
metadataFactory metadatainformers.SharedInformerFactory,
polInformer kyvernov1informers.PolicyInformer,
cpolInformer kyvernov1informers.ClusterPolicyInformer,
vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer,
nsInformer corev1informers.NamespaceInformer,
metadataCache resource.MetadataCache,
forceDelay time.Duration,
@ -104,6 +109,14 @@ func NewController(
eventGen: eventGen,
policyReports: policyReports,
}
if vapInformer != nil {
c.vapLister = vapInformer.Lister()
if _, err := controllerutils.AddEventHandlersT(vapInformer.Informer(), c.addVAP, c.updateVAP, c.deleteVAP); err != nil {
logger.Error(err, "failed to register even handlers")
}
}
if _, _, err := controllerutils.AddDefaultEventHandlers(logger, bgscanr.Informer(), queue); err != nil {
logger.Error(err, "failed to register even handlers")
}
@ -149,6 +162,20 @@ func (c *controller) deletePolicy(obj kyvernov1.PolicyInterface) {
c.enqueueResources()
}
func (c *controller) addVAP(obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) {
c.enqueueResources()
}
func (c *controller) updateVAP(old, obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) {
if old.GetResourceVersion() != obj.GetResourceVersion() {
c.enqueueResources()
}
}
func (c *controller) deleteVAP(obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) {
c.enqueueResources()
}
func (c *controller) enqueueResources() {
for _, key := range c.metadataCache.GetAllResourceKeys() {
c.queue.Add(key)
@ -179,7 +206,7 @@ func (c *controller) getMeta(namespace, name string) (metav1.Object, error) {
}
}
func (c *controller) needsReconcile(namespace, name, hash string, backgroundPolicies ...kyvernov1.PolicyInterface) (bool, bool, error) {
func (c *controller) needsReconcile(namespace, name, hash string, policies ...engineapi.GenericPolicy) (bool, bool, error) {
// if the reportMetadata does not exist, we need a full reconcile
reportMetadata, err := c.getMeta(namespace, name)
if err != nil {
@ -208,7 +235,7 @@ func (c *controller) needsReconcile(namespace, name, hash string, backgroundPoli
}
// if a policy changed, we need a partial reconcile
expected := map[string]string{}
for _, policy := range backgroundPolicies {
for _, policy := range policies {
expected[reportutils.PolicyLabel(policy)] = policy.GetResourceVersion()
}
actual := map[string]string{}
@ -232,7 +259,7 @@ func (c *controller) reconcileReport(
uid types.UID,
gvk schema.GroupVersionKind,
resource resource.Resource,
backgroundPolicies ...kyvernov1.PolicyInterface,
policies ...engineapi.GenericPolicy,
) error {
// namespace labels to be used by the scanner
var nsLabels map[string]string
@ -258,7 +285,7 @@ func (c *controller) reconcileReport(
}
// build desired report
expected := map[string]string{}
for _, policy := range backgroundPolicies {
for _, policy := range policies {
expected[reportutils.PolicyLabel(policy)] = policy.GetResourceVersion()
}
actual := map[string]string{}
@ -270,8 +297,14 @@ func (c *controller) reconcileReport(
var ruleResults []policyreportv1alpha2.PolicyReportResult
if !full {
policyNameToLabel := map[string]string{}
for _, policy := range backgroundPolicies {
key, err := cache.MetaNamespaceKeyFunc(policy)
for _, policy := range policies {
var key string
var err error
if policy.GetType() == engineapi.KyvernoPolicyType {
key, err = cache.MetaNamespaceKeyFunc(policy.GetPolicy().(kyvernov1.PolicyInterface))
} else {
key, err = cache.MetaNamespaceKeyFunc(policy.GetPolicy().(admissionregistrationv1alpha1.ValidatingAdmissionPolicy))
}
if err != nil {
return err
}
@ -287,7 +320,7 @@ func (c *controller) reconcileReport(
}
}
// calculate necessary results
for _, policy := range backgroundPolicies {
for _, policy := range policies {
if full || actual[reportutils.PolicyLabel(policy)] != policy.GetResourceVersion() {
scanner := utils.NewScanner(logger, c.engine, c.config, c.jp)
for _, result := range scanner.ScanResource(ctx, *target, nsLabels, policy) {
@ -306,7 +339,7 @@ func (c *controller) reconcileReport(
delete(desired.GetLabels(), key)
}
}
for _, policy := range backgroundPolicies {
for _, policy := range policies {
reportutils.SetPolicyLabel(desired, policy)
}
reportutils.SetResourceVersionLabels(desired, target)
@ -365,8 +398,8 @@ func (c *controller) reconcile(ctx context.Context, log logr.Logger, key, namesp
}
}
}
// load all policies
policies, err := utils.FetchClusterPolicies(c.cpolLister)
// load all kyverno policies
kyvernoPolicies, err := utils.FetchClusterPolicies(c.cpolLister)
if err != nil {
return err
}
@ -375,22 +408,33 @@ func (c *controller) reconcile(ctx context.Context, log logr.Logger, key, namesp
if err != nil {
return err
}
policies = append(policies, pols...)
kyvernoPolicies = append(kyvernoPolicies, pols...)
}
// load background policies
backgroundPolicies := utils.RemoveNonBackgroundPolicies(policies...)
if err != nil {
return err
kyvernoPolicies = utils.RemoveNonBackgroundPolicies(kyvernoPolicies...)
var policies []engineapi.GenericPolicy
for _, pol := range kyvernoPolicies {
policies = append(policies, engineapi.NewKyvernoPolicy(pol))
}
if c.vapLister != nil {
// load validating admission policies
vapPolicies, err := utils.FetchValidatingAdmissionPolicies(c.vapLister)
if err != nil {
return err
}
for _, pol := range vapPolicies {
policies = append(policies, engineapi.NewValidatingAdmissionPolicy(pol))
}
}
// we have the resource, check if we need to reconcile
if needsReconcile, full, err := c.needsReconcile(namespace, name, resource.Hash, backgroundPolicies...); err != nil {
if needsReconcile, full, err := c.needsReconcile(namespace, name, resource.Hash, policies...); err != nil {
return err
} else {
defer func() {
c.queue.AddAfter(key, c.forceDelay)
}()
if needsReconcile {
return c.reconcileReport(ctx, namespace, name, full, uid, gvk, resource, backgroundPolicies...)
return c.reconcileReport(ctx, namespace, name, full, uid, gvk, resource, policies...)
}
}
return nil

View file

@ -15,6 +15,7 @@ import (
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
reportutils "github.com/kyverno/kyverno/pkg/utils/report"
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
"golang.org/x/exp/slices"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -22,6 +23,8 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
admissionregistrationv1alpha1informers "k8s.io/client-go/informers/admissionregistration/v1alpha1"
admissionregistrationv1alpha1listers "k8s.io/client-go/listers/admissionregistration/v1alpha1"
"k8s.io/client-go/tools/cache"
watchTools "k8s.io/client-go/tools/watch"
"k8s.io/client-go/util/workqueue"
@ -76,6 +79,7 @@ type controller struct {
// listers
polLister kyvernov1listers.PolicyLister
cpolLister kyvernov1listers.ClusterPolicyLister
vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister
// queue
queue workqueue.RateLimitingInterface
@ -89,6 +93,7 @@ func NewController(
client dclient.Interface,
polInformer kyvernov1informers.PolicyInformer,
cpolInformer kyvernov1informers.ClusterPolicyInformer,
vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer,
) Controller {
c := controller{
client: client,
@ -97,6 +102,14 @@ func NewController(
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), ControllerName),
dynamicWatchers: map[schema.GroupVersionResource]*watcher{},
}
if vapInformer != nil {
c.vapLister = vapInformer.Lister()
if _, _, err := controllerutils.AddDefaultEventHandlers(logger, vapInformer.Informer(), c.queue); err != nil {
logger.Error(err, "failed to register even handlers")
}
}
if _, _, err := controllerutils.AddDefaultEventHandlers(logger, polInformer.Informer(), c.queue); err != nil {
logger.Error(err, "failed to register even handlers")
}
@ -226,23 +239,19 @@ func (c *controller) updateDynamicWatchers(ctx context.Context) error {
gvkToGvr := map[schema.GroupVersionKind]schema.GroupVersionResource{}
for _, policyKind := range sets.List(kinds) {
group, version, kind, subresource := kubeutils.ParseKindSelector(policyKind)
gvrss, err := c.client.Discovery().FindResources(group, version, kind, subresource)
c.addGVKToGVRMapping(group, version, kind, subresource, gvkToGvr)
}
if c.vapLister != nil {
vapPolicies, err := utils.FetchValidatingAdmissionPolicies(c.vapLister)
if err != nil {
logger.Error(err, "failed to get gvr from kind", "kind", kind)
} else {
for gvrs, api := range gvrss {
if gvrs.SubResource == "" {
gvk := schema.GroupVersionKind{Group: gvrs.Group, Version: gvrs.Version, Kind: policyKind}
if !reportutils.IsGvkSupported(gvk) {
logger.Info("kind is not supported", "gvk", gvk)
} else {
if slices.Contains(api.Verbs, "list") && slices.Contains(api.Verbs, "watch") {
gvkToGvr[gvk] = gvrs.GroupVersionResource()
} else {
logger.Info("list/watch not supported for kind", "kind", kind)
}
}
}
return err
}
// fetch kinds from validating admission policies
for _, policy := range vapPolicies {
kinds := validatingadmissionpolicy.GetKinds(policy)
for _, kind := range kinds {
group, version, kind, subresource := kubeutils.ParseKindSelector(kind)
c.addGVKToGVRMapping(group, version, kind, subresource, gvkToGvr)
}
}
}
@ -274,6 +283,28 @@ func (c *controller) updateDynamicWatchers(ctx context.Context) error {
return nil
}
func (c *controller) addGVKToGVRMapping(group, version, kind, subresource string, gvrMap map[schema.GroupVersionKind]schema.GroupVersionResource) {
gvrss, err := c.client.Discovery().FindResources(group, version, kind, subresource)
if err != nil {
logger.Error(err, "failed to get gvr from kind", "kind", kind)
} else {
for gvrs, api := range gvrss {
if gvrs.SubResource == "" {
gvk := schema.GroupVersionKind{Group: gvrs.Group, Version: gvrs.Version, Kind: kind}
if !reportutils.IsGvkSupported(gvk) {
logger.Info("kind is not supported", "gvk", gvk)
} else {
if slices.Contains(api.Verbs, "list") && slices.Contains(api.Verbs, "watch") {
gvrMap[gvk] = gvrs.GroupVersionResource()
} else {
logger.Info("list/watch not supported for kind", "kind", kind)
}
}
}
}
}
}
func (c *controller) stopDynamicWatchers() {
c.lock.Lock()
defer c.lock.Unlock()

View file

@ -10,7 +10,9 @@ import (
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
"go.uber.org/multierr"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -27,7 +29,7 @@ type ScanResult struct {
}
type Scanner interface {
ScanResource(context.Context, unstructured.Unstructured, map[string]string, ...kyvernov1.PolicyInterface) map[kyvernov1.PolicyInterface]ScanResult
ScanResource(context.Context, unstructured.Unstructured, map[string]string, ...engineapi.GenericPolicy) map[*engineapi.GenericPolicy]ScanResult
}
func NewScanner(
@ -44,30 +46,39 @@ func NewScanner(
}
}
func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstructured, nsLabels map[string]string, policies ...kyvernov1.PolicyInterface) map[kyvernov1.PolicyInterface]ScanResult {
results := map[kyvernov1.PolicyInterface]ScanResult{}
for _, policy := range policies {
func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstructured, nsLabels map[string]string, policies ...engineapi.GenericPolicy) map[*engineapi.GenericPolicy]ScanResult {
results := map[*engineapi.GenericPolicy]ScanResult{}
for i, policy := range policies {
var errors []error
logger := s.logger.WithValues("kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
response, err := s.validateResource(ctx, resource, nsLabels, policy)
if err != nil {
logger.Error(err, "failed to scan resource")
errors = append(errors, err)
}
spec := policy.GetSpec()
if spec.HasVerifyImages() {
ivResponse, err := s.validateImages(ctx, resource, nsLabels, policy)
var response *engineapi.EngineResponse
if policy.GetType() == engineapi.KyvernoPolicyType {
var err error
pol := policy.GetPolicy().(kyvernov1.PolicyInterface)
response, err = s.validateResource(ctx, resource, nsLabels, pol)
if err != nil {
logger.Error(err, "failed to scan images")
logger.Error(err, "failed to scan resource")
errors = append(errors, err)
}
if response == nil {
response = ivResponse
} else if ivResponse != nil {
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ivResponse.PolicyResponse.Rules...)
spec := pol.GetSpec()
if spec.HasVerifyImages() && len(errors) == 0 {
ivResponse, err := s.validateImages(ctx, resource, nsLabels, pol)
if err != nil {
logger.Error(err, "failed to scan images")
errors = append(errors, err)
}
if response == nil {
response = ivResponse
} else if ivResponse != nil {
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ivResponse.PolicyResponse.Rules...)
}
}
} else {
pol := policy.GetPolicy().(admissionregistrationv1alpha1.ValidatingAdmissionPolicy)
res := validatingadmissionpolicy.Validate(pol, resource)
response = &res
}
results[policy] = ScanResult{response, multierr.Combine(errors...)}
results[&policies[i]] = ScanResult{response, multierr.Combine(errors...)}
}
return results
}

View file

@ -8,9 +8,11 @@ import (
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets"
admissionregistrationv1alpha1listers "k8s.io/client-go/listers/admissionregistration/v1alpha1"
)
func CanBackgroundProcess(p kyvernov1.PolicyInterface) bool {
@ -103,3 +105,15 @@ func FetchPolicies(polLister kyvernov1listers.PolicyLister, namespace string) ([
}
return policies, nil
}
func FetchValidatingAdmissionPolicies(vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister) ([]admissionregistrationv1alpha1.ValidatingAdmissionPolicy, error) {
var policies []admissionregistrationv1alpha1.ValidatingAdmissionPolicy
if pols, err := vapLister.List(labels.Everything()); err != nil {
return nil, err
} else {
for _, pol := range pols {
policies = append(policies, *pol)
}
}
return policies, nil
}

View file

@ -26,8 +26,14 @@ type GenericPolicy interface {
GetName() string
// GetNamespace returns policy namespace
GetNamespace() string
// GetKind returns policy kind
GetKind() string
// GetResourceVersion returns policy resource version
GetResourceVersion() string
// GetAnnotations returns policy annotations
GetAnnotations() map[string]string
// IsNamespaced indicates if the policy is namespace scoped
IsNamespaced() bool
}
type KyvernoPolicy struct {
@ -50,10 +56,22 @@ func (p *KyvernoPolicy) GetNamespace() string {
return p.policy.GetNamespace()
}
func (p *KyvernoPolicy) GetKind() string {
return p.policy.GetKind()
}
func (p *KyvernoPolicy) GetResourceVersion() string {
return p.policy.GetResourceVersion()
}
func (p *KyvernoPolicy) GetAnnotations() map[string]string {
return p.policy.GetAnnotations()
}
func (p *KyvernoPolicy) IsNamespaced() bool {
return p.policy.IsNamespaced()
}
func NewKyvernoPolicy(pol kyvernov1.PolicyInterface) GenericPolicy {
return &KyvernoPolicy{
policy: pol,
@ -80,10 +98,22 @@ func (p *ValidatingAdmissionPolicy) GetNamespace() string {
return p.policy.GetNamespace()
}
func (p *ValidatingAdmissionPolicy) GetKind() string {
return p.policy.Kind
}
func (p *ValidatingAdmissionPolicy) GetResourceVersion() string {
return p.policy.GetResourceVersion()
}
func (p *ValidatingAdmissionPolicy) GetAnnotations() map[string]string {
return p.policy.GetAnnotations()
}
func (p *ValidatingAdmissionPolicy) IsNamespaced() bool {
return false
}
func NewValidatingAdmissionPolicy(pol v1alpha1.ValidatingAdmissionPolicy) GenericPolicy {
return &ValidatingAdmissionPolicy{
policy: pol,

View file

@ -16,10 +16,10 @@ func NewPolicyFailEvent(source Source, reason Reason, engineResponse engineapi.E
action = ResourceBlocked
}
pol := engineResponse.Policy().GetPolicy().(kyvernov1.PolicyInterface)
pol := engineResponse.Policy()
return Info{
Kind: getPolicyKind(pol),
Kind: pol.GetKind(),
Name: pol.GetName(),
Namespace: pol.GetNamespace(),
RelatedAPIVersion: engineResponse.GetResourceSpec().APIVersion,
@ -53,13 +53,6 @@ func buildPolicyEventMessage(resp engineapi.RuleResponse, resource engineapi.Res
return b.String()
}
func getPolicyKind(policy kyvernov1.PolicyInterface) string {
if policy.IsNamespaced() {
return "Policy"
}
return "ClusterPolicy"
}
func getCleanupPolicyKind(policy kyvernov2alpha1.CleanupPolicyInterface) string {
if policy.IsNamespaced() {
return "CleanupPolicy"
@ -94,7 +87,7 @@ func NewPolicyAppliedEvent(source Source, engineResponse engineapi.EngineRespons
}
return Info{
Kind: getPolicyKind(pol),
Kind: pol.GetKind(),
Name: pol.GetName(),
Namespace: pol.GetNamespace(),
RelatedAPIVersion: resource.GetAPIVersion(),
@ -112,7 +105,7 @@ func NewResourceViolationEvent(source Source, reason Reason, engineResponse engi
var bldr strings.Builder
defer bldr.Reset()
pol := engineResponse.Policy().GetPolicy().(kyvernov1.PolicyInterface)
pol := engineResponse.Policy()
fmt.Fprintf(&bldr, "policy %s/%s %s: %s", pol.GetName(),
ruleResp.Name(), ruleResp.Status(), ruleResp.Message())
resource := engineResponse.GetResourceSpec()
@ -202,7 +195,7 @@ func NewPolicyExceptionEvents(engineResponse engineapi.EngineResponse, ruleResp
exceptionMessage = fmt.Sprintf("resource %s was skipped from policy rule %s/%s/%s", resourceKey(engineResponse.PatchedResource), pol.GetNamespace(), pol.GetName(), ruleResp.Name())
}
policyEvent := Info{
Kind: getPolicyKind(pol),
Kind: pol.GetKind(),
Name: pol.GetName(),
Namespace: pol.GetNamespace(),
RelatedAPIVersion: engineResponse.PatchedResource.GetAPIVersion(),

View file

@ -10,6 +10,7 @@ import (
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1alpha2 "github.com/kyverno/kyverno/api/kyverno/v1alpha2"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -26,10 +27,11 @@ const (
LabelResourceNamespace = "audit.kyverno.io/resource.namespace"
LabelResourceName = "audit.kyverno.io/resource.name"
// policy labels
LabelDomainClusterPolicy = "cpol.kyverno.io"
LabelDomainPolicy = "pol.kyverno.io"
LabelPrefixClusterPolicy = LabelDomainClusterPolicy + "/"
LabelPrefixPolicy = LabelDomainPolicy + "/"
LabelDomainClusterPolicy = "cpol.kyverno.io"
LabelDomainPolicy = "pol.kyverno.io"
LabelPrefixClusterPolicy = LabelDomainClusterPolicy + "/"
LabelPrefixPolicy = LabelDomainPolicy + "/"
LabelPrefixValidatingAdmissionPolicy = "validatingadmissionpolicy.apiserver.io/"
// aggregated admission report label
LabelAggregatedReport = "audit.kyverno.io/report.aggregate"
)
@ -50,11 +52,14 @@ func PolicyNameFromLabel(namespace, label string) (string, error) {
return "", fmt.Errorf("cannot get policy name from label, incorrect format: %s", label)
}
func PolicyLabelPrefix(policy kyvernov1.PolicyInterface) string {
func PolicyLabelPrefix(policy engineapi.GenericPolicy) string {
if policy.IsNamespaced() {
return LabelPrefixPolicy
}
return LabelPrefixClusterPolicy
if policy.GetType() == engineapi.KyvernoPolicyType {
return LabelPrefixClusterPolicy
}
return LabelPrefixValidatingAdmissionPolicy
}
func PolicyLabelDomain(policy kyvernov1.PolicyInterface) string {
@ -64,7 +69,7 @@ func PolicyLabelDomain(policy kyvernov1.PolicyInterface) string {
return LabelDomainClusterPolicy
}
func PolicyLabel(policy kyvernov1.PolicyInterface) string {
func PolicyLabel(policy engineapi.GenericPolicy) string {
return PolicyLabelPrefix(policy) + policy.GetName()
}
@ -125,7 +130,7 @@ func SetResourceVersionLabels(report kyvernov1alpha2.ReportInterface, resource *
}
}
func SetPolicyLabel(report kyvernov1alpha2.ReportInterface, policy kyvernov1.PolicyInterface) {
func SetPolicyLabel(report kyvernov1alpha2.ReportInterface, policy engineapi.GenericPolicy) {
controllerutils.SetLabel(report, PolicyLabel(policy), policy.GetResourceVersion())
}

View file

@ -87,45 +87,60 @@ func SeverityFromString(severity string) policyreportv1alpha2.PolicySeverity {
}
func EngineResponseToReportResults(response engineapi.EngineResponse) []policyreportv1alpha2.PolicyReportResult {
pol := response.Policy().GetPolicy().(kyvernov1.PolicyInterface)
key, _ := cache.MetaNamespaceKeyFunc(pol)
pol := response.Policy()
var results []policyreportv1alpha2.PolicyReportResult
for _, ruleResult := range response.PolicyResponse.Rules {
annotations := pol.GetAnnotations()
result := policyreportv1alpha2.PolicyReportResult{
Source: kyverno.ValueKyvernoApp,
Policy: key,
Rule: ruleResult.Name(),
Message: ruleResult.Message(),
Result: toPolicyResult(ruleResult.Status()),
Scored: annotations[kyverno.AnnotationPolicyScored] != "false",
Timestamp: metav1.Timestamp{
Seconds: time.Now().Unix(),
},
Category: annotations[kyverno.AnnotationPolicyCategory],
Severity: SeverityFromString(annotations[kyverno.AnnotationPolicySeverity]),
}
pss := ruleResult.PodSecurityChecks()
if pss != nil {
var controls []string
for _, check := range pss.Checks {
if !check.CheckResult.Allowed {
controls = append(controls, check.ID)
if pol.GetType() == engineapi.KyvernoPolicyType {
key, _ := cache.MetaNamespaceKeyFunc(pol.GetPolicy().(kyvernov1.PolicyInterface))
for _, ruleResult := range response.PolicyResponse.Rules {
annotations := pol.GetAnnotations()
result := policyreportv1alpha2.PolicyReportResult{
Source: kyverno.ValueKyvernoApp,
Policy: key,
Rule: ruleResult.Name(),
Message: ruleResult.Message(),
Result: toPolicyResult(ruleResult.Status()),
Scored: annotations[kyverno.AnnotationPolicyScored] != "false",
Timestamp: metav1.Timestamp{
Seconds: time.Now().Unix(),
},
Category: annotations[kyverno.AnnotationPolicyCategory],
Severity: SeverityFromString(annotations[kyverno.AnnotationPolicySeverity]),
}
pss := ruleResult.PodSecurityChecks()
if pss != nil {
var controls []string
for _, check := range pss.Checks {
if !check.CheckResult.Allowed {
controls = append(controls, check.ID)
}
}
if len(controls) > 0 {
sort.Strings(controls)
result.Properties = map[string]string{
"standard": string(pss.Level),
"version": pss.Version,
"controls": strings.Join(controls, ","),
}
}
}
if len(controls) > 0 {
sort.Strings(controls)
result.Properties = map[string]string{
"standard": string(pss.Level),
"version": pss.Version,
"controls": strings.Join(controls, ","),
}
if result.Result == "fail" && !result.Scored {
result.Result = "warn"
}
results = append(results, result)
}
if result.Result == "fail" && !result.Scored {
result.Result = "warn"
} else {
for _, ruleResult := range response.PolicyResponse.Rules {
result := policyreportv1alpha2.PolicyReportResult{
Source: "ValidatingAdmissionPolicy",
Policy: ruleResult.Name(),
Message: ruleResult.Message(),
Result: toPolicyResult(ruleResult.Status()),
Timestamp: metav1.Timestamp{
Seconds: time.Now().Unix(),
},
}
results = append(results, result)
}
results = append(results, result)
}
return results
}
@ -163,7 +178,7 @@ func SetResults(report kyvernov1alpha2.ReportInterface, results ...policyreportv
func SetResponses(report kyvernov1alpha2.ReportInterface, engineResponses ...engineapi.EngineResponse) {
var ruleResults []policyreportv1alpha2.PolicyReportResult
for _, result := range engineResponses {
pol := result.Policy().GetPolicy().(kyvernov1.PolicyInterface)
pol := result.Policy()
SetPolicyLabel(report, pol)
ruleResults = append(ruleResults, EngineResponseToReportResults(result)...)
}

View file

@ -1,7 +1,7 @@
package report
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
@ -16,7 +16,7 @@ func SelectorResourceUidEquals(uid types.UID) (labels.Selector, error) {
return selector, err
}
func SelectorPolicyDoesNotExist(policy kyvernov1.PolicyInterface) (labels.Selector, error) {
func SelectorPolicyDoesNotExist(policy engineapi.GenericPolicy) (labels.Selector, error) {
selector := labels.Everything()
requirement, err := labels.NewRequirement(PolicyLabel(policy), selection.DoesNotExist, nil)
if err == nil {
@ -25,7 +25,7 @@ func SelectorPolicyDoesNotExist(policy kyvernov1.PolicyInterface) (labels.Select
return selector, err
}
func SelectorPolicyExists(policy kyvernov1.PolicyInterface) (labels.Selector, error) {
func SelectorPolicyExists(policy engineapi.GenericPolicy) (labels.Selector, error) {
selector := labels.Everything()
requirement, err := labels.NewRequirement(PolicyLabel(policy), selection.Exists, nil)
if err == nil {
@ -34,7 +34,7 @@ func SelectorPolicyExists(policy kyvernov1.PolicyInterface) (labels.Selector, er
return selector, err
}
func SelectorPolicyNotEquals(policy kyvernov1.PolicyInterface) (labels.Selector, error) {
func SelectorPolicyNotEquals(policy engineapi.GenericPolicy) (labels.Selector, error) {
selector := labels.Everything()
requirement, err := labels.NewRequirement(PolicyLabel(policy), selection.NotEquals, []string{policy.GetResourceVersion()})
if err == nil {