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

feat: skip generating VAP when an exception is defined (#9386)

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2024-01-18 17:55:27 +02:00 committed by GitHub
parent b4acbdea2c
commit a791d9ac35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 183 additions and 1 deletions

View file

@ -47,6 +47,10 @@ func (p *PolicyException) Contains(policy string, rule string) bool {
return p.Spec.Contains(policy, rule)
}
func (p *PolicyException) GetKind() string {
return "PolicyException"
}
// PolicyExceptionSpec stores policy exception spec
type PolicyExceptionSpec struct {
// Background controls if exceptions are applied to existing policies during a background scan.

View file

@ -40,6 +40,10 @@ func (p *PolicyException) Contains(policy string, rule string) bool {
return p.Spec.Contains(policy, rule)
}
func (p *PolicyException) GetKind() string {
return "PolicyException"
}
// Exception stores infos about a policy and rules
type Exception = kyvernov2beta1.Exception

View file

@ -47,6 +47,10 @@ func (p *PolicyException) Contains(policy string, rule string) bool {
return p.Spec.Contains(policy, rule)
}
func (p *PolicyException) GetKind() string {
return "PolicyException"
}
// PolicyExceptionSpec stores policy exception spec
type PolicyExceptionSpec struct {
// Background controls if exceptions are applied to existing policies during a background scan.

View file

@ -192,6 +192,7 @@ func createrLeaderControllers(
kyvernoClient,
dynamicClient.Discovery(),
kyvernoInformer.Kyverno().V1().ClusterPolicies(),
kyvernoInformer.Kyverno().V2beta1().PolicyExceptions(),
kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies(),
kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings(),
eventGenerator,

View file

@ -2,14 +2,18 @@ package validatingadmissionpolicygenerate
import (
"context"
"strings"
"time"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
"github.com/kyverno/kyverno/pkg/auth/checker"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernov2beta1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v2beta1"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
kyvernov2beta1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2beta1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/controllers"
"github.com/kyverno/kyverno/pkg/event"
@ -43,6 +47,7 @@ type controller struct {
// listers
cpolLister kyvernov1listers.ClusterPolicyLister
polexLister kyvernov2beta1listers.PolicyExceptionLister
vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister
vapbindingLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyBindingLister
@ -58,6 +63,7 @@ func NewController(
kyvernoClient versioned.Interface,
discoveryClient dclient.IDiscovery,
cpolInformer kyvernov1informers.ClusterPolicyInformer,
polexInformer kyvernov2beta1informers.PolicyExceptionInformer,
vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer,
vapbindingInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyBindingInformer,
eventGen event.Interface,
@ -69,6 +75,7 @@ func NewController(
kyvernoClient: kyvernoClient,
discoveryClient: discoveryClient,
cpolLister: cpolInformer.Lister(),
polexLister: polexInformer.Lister(),
vapLister: vapInformer.Lister(),
vapbindingLister: vapbindingInformer.Lister(),
queue: queue,
@ -81,6 +88,11 @@ func NewController(
logger.Error(err, "failed to register event handlers")
}
// Set up an event handler for when policy exceptions change
if _, err := controllerutils.AddEventHandlersT(polexInformer.Informer(), c.addException, c.updateException, c.deleteException); err != nil {
logger.Error(err, "failed to register event handlers")
}
// Set up an event handler for when validating admission policies change
if _, err := controllerutils.AddEventHandlersT(vapInformer.Informer(), c.addVAP, c.updateVAP, c.deleteVAP); err != nil {
logger.Error(err, "failed to register event handlers")
@ -135,6 +147,46 @@ func (c *controller) enqueuePolicy(obj kyvernov1.PolicyInterface) {
c.queue.Add(key)
}
func (c *controller) addException(obj *kyvernov2beta1.PolicyException) {
logger.Info("policy exception created", "uid", obj.GetUID(), "kind", obj.GetKind(), "name", obj.GetName())
c.enqueueException(obj)
}
func (c *controller) updateException(old, obj *kyvernov2beta1.PolicyException) {
if datautils.DeepEqual(old.Spec, obj.Spec) {
return
}
logger.Info("policy exception updated", "uid", obj.GetUID(), "kind", obj.GetKind(), "name", obj.GetName())
c.enqueueException(obj)
}
func (c *controller) deleteException(obj *kyvernov2beta1.PolicyException) {
polex := kubeutils.GetObjectWithTombstone(obj).(*kyvernov2beta1.PolicyException)
logger.Info("policy exception deleted", "uid", polex.GetUID(), "kind", polex.GetKind(), "name", polex.GetName())
c.enqueueException(obj)
}
func (c *controller) enqueueException(obj *kyvernov2beta1.PolicyException) {
for _, exception := range obj.Spec.Exceptions {
// skip adding namespaced policies in the queue.
// skip adding policies with multiple rules in the queue.
if strings.Contains(exception.PolicyName, "/") || len(exception.RuleNames) > 1 {
continue
}
cpol, err := c.getClusterPolicy(exception.PolicyName)
if err != nil {
if apierrors.IsNotFound(err) {
return
}
logger.Error(err, "unable to get the policy from policy informer")
return
}
c.enqueuePolicy(cpol)
}
}
func (c *controller) addVAP(obj *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) {
c.enqueueVAP(obj)
}
@ -334,7 +386,12 @@ func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, nam
observedVAP, vapErr := c.getValidatingAdmissionPolicy(vapName)
observedVAPbinding, vapBindingErr := c.getValidatingAdmissionPolicyBinding(vapBindingName)
if ok, msg := canGenerateVAP(spec); !ok {
hasExceptions, err := c.hasExceptions(name, spec.Rules[0].Name)
if err != nil {
return err
}
if ok, msg := canGenerateVAP(spec); !ok || hasExceptions {
// delete the ValidatingAdmissionPolicy if exist
if vapErr == nil {
err = c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Delete(ctx, vapName, metav1.DeleteOptions{})
@ -349,6 +406,10 @@ func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, nam
return err
}
}
if msg == "" {
msg = "skip generating ValidatingAdmissionPolicy: a policy exception is configured."
}
c.updateClusterPolicyStatus(ctx, *policy, false, msg)
return nil
}

View file

@ -2,8 +2,23 @@ package validatingadmissionpolicygenerate
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"k8s.io/apimachinery/pkg/labels"
)
// hasExceptions checks if there is an exception that match both the policy and the rule.
func (c *controller) hasExceptions(policyName, rule string) (bool, error) {
polexs, err := c.polexLister.List(labels.Everything())
if err != nil {
return false, err
}
for _, polex := range polexs {
if polex.Contains(policyName, rule) {
return true, nil
}
}
return false, nil
}
func checkResources(resource kyvernov1.ResourceDescription) (bool, string) {
var msg string
if len(resource.Namespaces) != 0 || len(resource.Annotations) != 0 {

View file

@ -0,0 +1,27 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: cpol-with-exceptions
spec:
steps:
- name: step-01
try:
- apply:
file: policy.yaml
- assert:
file: policy-assert.yaml
- name: step-02
try:
- apply:
file: exception.yaml
- name: step-03
try:
- sleep:
duration: 15s
- name: step-04
try:
- error:
file: validatingadmissionpolicy.yaml
- error:
file: validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,16 @@
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
name: policy-exception
spec:
exceptions:
- policyName: disallow-host-path-t11
ruleNames:
- host-path
match:
any:
- resources:
kinds:
- Deployment
names:
- important-tool*

View file

@ -0,0 +1,10 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t11
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,26 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t11
spec:
validationFailureAction: Audit
background: false
rules:
- name: host-path
match:
all:
- resources:
kinds:
- Deployment
- StatefulSet
operations:
- CREATE
- UPDATE
selector:
matchLabels:
app: critical
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t11
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t11-binding
spec: {}