diff --git a/api/policies.kyverno.io/v1alpha1/imageverification_policy.go b/api/policies.kyverno.io/v1alpha1/imageverification_policy.go index 489b729323..4120da051e 100644 --- a/api/policies.kyverno.io/v1alpha1/imageverification_policy.go +++ b/api/policies.kyverno.io/v1alpha1/imageverification_policy.go @@ -76,6 +76,22 @@ func (s *ImageVerificationPolicy) GetKind() string { return "ImageVerificationPolicy" } +// AdmissionEnabled checks if admission is set to true +func (s ImageVerificationPolicySpec) AdmissionEnabled() bool { + if s.EvaluationConfiguration == nil || s.EvaluationConfiguration.Admission == nil || s.EvaluationConfiguration.Admission.Enabled == nil { + return true + } + return *s.EvaluationConfiguration.Admission.Enabled +} + +// BackgroundEnabled checks if background is set to true +func (s ImageVerificationPolicySpec) BackgroundEnabled() bool { + if s.EvaluationConfiguration == nil || s.EvaluationConfiguration.Background == nil || s.EvaluationConfiguration.Background.Enabled == nil { + return true + } + return *s.EvaluationConfiguration.Background.Enabled +} + // +kubebuilder:object:root=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/api/policies.kyverno.io/v1alpha1/policy_interface.go b/api/policies.kyverno.io/v1alpha1/policy_interface.go index da782730a5..367c23292e 100644 --- a/api/policies.kyverno.io/v1alpha1/policy_interface.go +++ b/api/policies.kyverno.io/v1alpha1/policy_interface.go @@ -13,6 +13,5 @@ type GenericPolicy interface { GetFailurePolicy() admissionregistrationv1.FailurePolicyType GetWebhookConfiguration() *WebhookConfiguration GetVariables() []admissionregistrationv1.Variable - GetSpec() *ValidatingPolicySpec GetStatus() *PolicyStatus } diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index bf790bf0b6..cfc54873ba 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -159,6 +159,7 @@ func createrLeaderControllers( kyvernoInformer.Kyverno().V1().ClusterPolicies(), kyvernoInformer.Kyverno().V1().Policies(), kyvernoInformer.Policies().V1alpha1().ValidatingPolicies(), + kyvernoInformer.Policies().V1alpha1().ImageVerificationPolicies(), deploymentInformer, caInformer, kubeKyvernoInformer.Coordination().V1().Leases(), diff --git a/pkg/cel/autogen/autogen.go b/pkg/cel/autogen/autogen.go index d91fc3c4b8..14d54c7208 100644 --- a/pkg/cel/autogen/autogen.go +++ b/pkg/cel/autogen/autogen.go @@ -85,7 +85,7 @@ func stripCronJob(controllers string) (bool, string) { return isRemoved, strings.Join(newControllers, ",") } -func ComputeRules(policy policiesv1alpha1.GenericPolicy) []policiesv1alpha1.AutogenRule { +func ComputeRules(policy *policiesv1alpha1.ValidatingPolicy) []policiesv1alpha1.AutogenRule { applyAutoGen, desiredControllers := CanAutoGen(policy.GetSpec().MatchConstraints) if !applyAutoGen { return []policiesv1alpha1.AutogenRule{} diff --git a/pkg/config/config.go b/pkg/config/config.go index cdc46e5205..10c78bc38f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -63,7 +63,7 @@ const ( // ValidatingWebhookServicePath is the path for validation webhook ValidatingWebhookServicePath = "/validate" // ValidatingPolicyServicePath is the path for validating policies execution - ValidatingPolicyServicePath = "/vpol" + ValidatingPolicyServicePath = "/policies" // ExceptionValidatingWebhookServicePath is the path for policy exception validation webhook(used to validate policy exception resource) ExceptionValidatingWebhookServicePath = "/exceptionvalidate" // CELExceptionValidatingWebhookServicePath is the path for CELPolicyException validation webhook(used to validate CELPolicyException resource) diff --git a/pkg/controllers/webhook/controller.go b/pkg/controllers/webhook/controller.go index 469aaac2af..cd96ebde03 100644 --- a/pkg/controllers/webhook/controller.go +++ b/pkg/controllers/webhook/controller.go @@ -10,7 +10,6 @@ import ( "github.com/go-logr/logr" "github.com/kyverno/kyverno/api/kyverno" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1" "github.com/kyverno/kyverno/ext/wildcard" "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/client/clientset/versioned" @@ -21,6 +20,7 @@ import ( "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/controllers" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/tls" controllerutils "github.com/kyverno/kyverno/pkg/utils/controller" datautils "github.com/kyverno/kyverno/pkg/utils/data" @@ -100,6 +100,7 @@ type controller struct { cpolLister kyvernov1listers.ClusterPolicyLister polLister kyvernov1listers.PolicyLister vpolLister policiesv1alpha1listers.ValidatingPolicyLister + ivpolLister policiesv1alpha1listers.ImageVerificationPolicyLister deploymentLister appsv1listers.DeploymentLister secretLister corev1listers.SecretLister leaseLister coordinationv1listers.LeaseLister @@ -141,6 +142,7 @@ func NewController( cpolInformer kyvernov1informers.ClusterPolicyInformer, polInformer kyvernov1informers.PolicyInformer, vpolInformer policiesv1alpha1informers.ValidatingPolicyInformer, + ivpolInformer policiesv1alpha1informers.ImageVerificationPolicyInformer, deploymentInformer appsv1informers.DeploymentInformer, secretInformer corev1informers.SecretInformer, leaseInformer coordinationv1informers.LeaseInformer, @@ -174,6 +176,7 @@ func NewController( cpolLister: cpolInformer.Lister(), polLister: polInformer.Lister(), vpolLister: vpolInformer.Lister(), + ivpolLister: ivpolInformer.Lister(), deploymentLister: deploymentInformer.Lister(), secretLister: secretInformer.Lister(), leaseLister: leaseInformer.Lister(), @@ -257,6 +260,22 @@ func NewController( ); err != nil { logger.Error(err, "failed to register event handlers") } + if _, err := controllerutils.AddEventHandlers( + vpolInformer.Informer(), + func(interface{}) { c.enqueueResourceWebhooks(0) }, + func(interface{}, interface{}) { c.enqueueResourceWebhooks(0) }, + func(interface{}) { c.enqueueResourceWebhooks(0) }, + ); err != nil { + logger.Error(err, "failed to register event handlers") + } + if _, err := controllerutils.AddEventHandlers( + ivpolInformer.Informer(), + func(interface{}) { c.enqueueResourceWebhooks(0) }, + func(interface{}, interface{}) { c.enqueueResourceWebhooks(0) }, + func(interface{}) { c.enqueueResourceWebhooks(0) }, + ); err != nil { + logger.Error(err, "failed to register event handlers") + } configuration.OnChanged(c.enqueueAll) return &c } @@ -378,7 +397,7 @@ func (c *controller) recordPolicyState(webhookConfigurationName string, policies } } -func (c *controller) recordValidatingPolicyState(validatingpolicies ...policiesv1alpha1.GenericPolicy) { +func (c *controller) recordValidatingPolicyState(validatingpolicies ...engineapi.GenericPolicy) { for _, policy := range validatingpolicies { c.vpolStateRecorder.Record(policy.GetName()) } @@ -949,14 +968,22 @@ func (c *controller) buildForValidatingPolicies(cfg config.Configuration, caBund return nil } - vpols, err := c.getValidatingPolicies() + var policies []engineapi.GenericPolicy + pols, err := c.getValidatingPolicies() if err != nil { return err } + policies = append(policies, pols...) - webhooks := buildWebhookRules(cfg, c.server, c.servicePort, caBundle, vpols) + ivpols, err := c.getImageVerificationPolicy() + if err != nil { + return err + } + policies = append(policies, ivpols...) + + webhooks := buildWebhookRules(cfg, c.server, c.servicePort, caBundle, policies) result.Webhooks = append(result.Webhooks, webhooks...) - c.recordValidatingPolicyState(vpols...) + c.recordValidatingPolicyState(policies...) return nil } @@ -1062,21 +1089,36 @@ func (c *controller) getAllPolicies() ([]kyvernov1.PolicyInterface, error) { return policies, nil } -func (c *controller) getValidatingPolicies() ([]policiesv1alpha1.GenericPolicy, error) { +func (c *controller) getValidatingPolicies() ([]engineapi.GenericPolicy, error) { validatingpolicies, err := c.vpolLister.List(labels.Everything()) if err != nil { return nil, err } - vpols := make([]policiesv1alpha1.GenericPolicy, 0) + vpols := make([]engineapi.GenericPolicy, 0) for _, vpol := range validatingpolicies { if vpol.Spec.AdmissionEnabled() { - vpols = append(vpols, vpol) + vpols = append(vpols, engineapi.NewValidatingPolicy(vpol)) } } return vpols, nil } +func (c *controller) getImageVerificationPolicy() ([]engineapi.GenericPolicy, error) { + policies, err := c.ivpolLister.List(labels.Everything()) + if err != nil { + return nil, err + } + + ivpols := make([]engineapi.GenericPolicy, 0) + for _, ivpol := range policies { + if ivpol.Spec.AdmissionEnabled() { + ivpols = append(ivpols, engineapi.NewImageVerificationPolicy(ivpol)) + } + } + return ivpols, nil +} + func (c *controller) getLease() (*coordinationv1.Lease, error) { return c.leaseLister.Leases(config.KyvernoNamespace()).Get("kyverno-health") } diff --git a/pkg/controllers/webhook/validatingpolicy.go b/pkg/controllers/webhook/validatingpolicy.go index 204bbcf738..a0d15bdab4 100644 --- a/pkg/controllers/webhook/validatingpolicy.go +++ b/pkg/controllers/webhook/validatingpolicy.go @@ -4,11 +4,12 @@ import ( policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1" "github.com/kyverno/kyverno/pkg/cel/autogen" "github.com/kyverno/kyverno/pkg/config" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" "k8s.io/utils/ptr" ) -func buildWebhookRules(cfg config.Configuration, server string, servicePort int32, caBundle []byte, vpols []policiesv1alpha1.GenericPolicy) (webhooks []admissionregistrationv1.ValidatingWebhook) { +func buildWebhookRules(cfg config.Configuration, server string, servicePort int32, caBundle []byte, policies []engineapi.GenericPolicy) (webhooks []admissionregistrationv1.ValidatingWebhook) { var ( webhookIgnoreList []admissionregistrationv1.ValidatingWebhook webhookFailList []admissionregistrationv1.ValidatingWebhook @@ -36,46 +37,57 @@ func buildWebhookRules(cfg config.Configuration, server string, servicePort int3 webhookIgnore.ObjectSelector = cfg.GetWebhook().ObjectSelector webhookFail.ObjectSelector = cfg.GetWebhook().ObjectSelector } - for _, vpol := range vpols { + for _, pol := range policies { + var p policiesv1alpha1.GenericPolicy + matchResource := &admissionregistrationv1.MatchResources{} + if vpol := pol.AsValidatingPolicy(); vpol != nil { + p = vpol + matchResource = vpol.Spec.MatchConstraints + } else if ivpol := pol.AsImageVerificationPolicy(); ivpol != nil { + p = ivpol + } + webhook := admissionregistrationv1.ValidatingWebhook{} - failurePolicyIgnore := vpol.GetFailurePolicy() == admissionregistrationv1.Ignore + failurePolicyIgnore := p.GetFailurePolicy() == admissionregistrationv1.Ignore if failurePolicyIgnore { webhook.FailurePolicy = ptr.To(admissionregistrationv1.Ignore) } else { webhook.FailurePolicy = ptr.To(admissionregistrationv1.Fail) } - for _, match := range vpol.GetMatchConstraints().ResourceRules { + for _, match := range p.GetMatchConstraints().ResourceRules { webhook.Rules = append(webhook.Rules, match.RuleWithOperations) } fineGrainedWebhook := false - if vpol.GetMatchConditions() != nil { - for _, m := range vpol.GetMatchConditions() { - if ok, _ := autogen.CanAutoGen(vpol.GetSpec().MatchConstraints); ok { + if p.GetMatchConditions() != nil { + for _, m := range p.GetMatchConditions() { + if ok, _ := autogen.CanAutoGen(matchResource); ok { webhook.MatchConditions = append(webhook.MatchConditions, admissionregistrationv1.MatchCondition{ Name: m.Name, Expression: "!(object.kind == 'Pod') || " + m.Expression, }) } else { - webhook.MatchConditions = vpol.GetMatchConditions() + webhook.MatchConditions = p.GetMatchConditions() } } fineGrainedWebhook = true } - if vpol.GetMatchConstraints().MatchPolicy != nil && *vpol.GetMatchConstraints().MatchPolicy == admissionregistrationv1.Exact { - webhook.MatchPolicy = vpol.GetMatchConstraints().MatchPolicy + if p.GetMatchConstraints().MatchPolicy != nil && *p.GetMatchConstraints().MatchPolicy == admissionregistrationv1.Exact { + webhook.MatchPolicy = p.GetMatchConstraints().MatchPolicy fineGrainedWebhook = true } - if vpol.GetWebhookConfiguration() != nil && vpol.GetWebhookConfiguration().TimeoutSeconds != nil { - webhook.TimeoutSeconds = vpol.GetWebhookConfiguration().TimeoutSeconds + if p.GetWebhookConfiguration() != nil && p.GetWebhookConfiguration().TimeoutSeconds != nil { + webhook.TimeoutSeconds = p.GetWebhookConfiguration().TimeoutSeconds fineGrainedWebhook = true } - for _, rule := range autogen.ComputeRules(vpol.(*policiesv1alpha1.ValidatingPolicy)) { - webhook.MatchConditions = append(webhook.MatchConditions, rule.MatchConditions...) - for _, match := range rule.MatchConstraints.ResourceRules { - webhook.Rules = append(webhook.Rules, match.RuleWithOperations) + if vpol, ok := p.(*policiesv1alpha1.ValidatingPolicy); ok { + for _, rule := range autogen.ComputeRules(vpol) { + webhook.MatchConditions = append(webhook.MatchConditions, rule.MatchConditions...) + for _, match := range rule.MatchConstraints.ResourceRules { + webhook.Rules = append(webhook.Rules, match.RuleWithOperations) + } } } @@ -83,12 +95,12 @@ func buildWebhookRules(cfg config.Configuration, server string, servicePort int3 webhook.SideEffects = &noneOnDryRun webhook.AdmissionReviewVersions = []string{"v1"} if failurePolicyIgnore { - webhook.Name = config.ValidatingPolicyWebhookName + "-ignore-finegrained-" + vpol.GetName() - webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, "/vpol/ignore"+config.FineGrainedWebhookPath+"/"+vpol.GetName()) + webhook.Name = config.ValidatingPolicyWebhookName + "-ignore-finegrained-" + p.GetName() + webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, config.ValidatingPolicyServicePath+"/ignore"+config.FineGrainedWebhookPath+"/"+p.GetName()) webhookIgnoreList = append(webhookIgnoreList, webhook) } else { - webhook.Name = config.ValidatingPolicyWebhookName + "-fail-finegrained-" + vpol.GetName() - webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, "/vpol/fail"+config.FineGrainedWebhookPath+"/"+vpol.GetName()) + webhook.Name = config.ValidatingPolicyWebhookName + "-fail-finegrained-" + p.GetName() + webhook.ClientConfig = newClientConfig(server, servicePort, caBundle, config.ValidatingPolicyServicePath+"/fail"+config.FineGrainedWebhookPath+"/"+p.GetName()) webhookFailList = append(webhookFailList, webhook) } } else { diff --git a/pkg/controllers/webhook/validatingpolicy_test.go b/pkg/controllers/webhook/validatingpolicy_test.go index c63880ffbf..1fdd5b70d9 100644 --- a/pkg/controllers/webhook/validatingpolicy_test.go +++ b/pkg/controllers/webhook/validatingpolicy_test.go @@ -5,6 +5,7 @@ import ( policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1" "github.com/kyverno/kyverno/pkg/config" + engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/stretchr/testify/assert" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -135,7 +136,7 @@ func TestBuildWebhookRules(t *testing.T) { expectedWebhooks: []admissionregistrationv1.ValidatingWebhook{ { Name: config.ValidatingPolicyWebhookName + "-ignore-finegrained-test-fine-grained-ignore", - ClientConfig: newClientConfig("", 0, nil, "/vpol/ignore"+config.FineGrainedWebhookPath+"/test-fine-grained-ignore"), + ClientConfig: newClientConfig("", 0, nil, "/policies/ignore"+config.FineGrainedWebhookPath+"/test-fine-grained-ignore"), Rules: []admissionregistrationv1.RuleWithOperations{ { Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, @@ -193,7 +194,7 @@ func TestBuildWebhookRules(t *testing.T) { expectedWebhooks: []admissionregistrationv1.ValidatingWebhook{ { Name: config.ValidatingPolicyWebhookName + "-fail-finegrained-test-fine-grained-fail", - ClientConfig: newClientConfig("", 0, nil, "/vpol/fail"+config.FineGrainedWebhookPath+"/test-fine-grained-fail"), + ClientConfig: newClientConfig("", 0, nil, "/policies/fail"+config.FineGrainedWebhookPath+"/test-fine-grained-fail"), Rules: []admissionregistrationv1.RuleWithOperations{ { Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, @@ -221,9 +222,9 @@ func TestBuildWebhookRules(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var vpols []policiesv1alpha1.GenericPolicy + var vpols []engineapi.GenericPolicy for _, vpol := range tt.vpols { - vpols = append(vpols, vpol) + vpols = append(vpols, engineapi.NewValidatingPolicy(vpol)) } webhooks := buildWebhookRules(config.NewDefaultConfiguration(false), "", 0, nil, vpols) assert.Equal(t, len(tt.expectedWebhooks), len(webhooks)) diff --git a/pkg/engine/api/policy.go b/pkg/engine/api/policy.go index f5d3e83b44..ade9bcd4db 100644 --- a/pkg/engine/api/policy.go +++ b/pkg/engine/api/policy.go @@ -26,6 +26,8 @@ type GenericPolicy interface { AsValidatingAdmissionPolicy() *admissionregistrationv1.ValidatingAdmissionPolicy // AsValidatingPolicy returns the validating policy AsValidatingPolicy() *policiesv1alpha1.ValidatingPolicy + // AsImageVerificationPolicy returns the imageverificationpolicy + AsImageVerificationPolicy() *policiesv1alpha1.ImageVerificationPolicy } type genericPolicy struct { @@ -34,6 +36,7 @@ type genericPolicy struct { ValidatingAdmissionPolicy *admissionregistrationv1.ValidatingAdmissionPolicy MutatingAdmissionPolicy *admissionregistrationv1alpha1.MutatingAdmissionPolicy ValidatingPolicy *policiesv1alpha1.ValidatingPolicy + ImageVerificationPolicy *policiesv1alpha1.ImageVerificationPolicy } func (p *genericPolicy) AsObject() any { @@ -52,6 +55,10 @@ func (p *genericPolicy) AsValidatingPolicy() *policiesv1alpha1.ValidatingPolicy return p.ValidatingPolicy } +func (p *genericPolicy) AsImageVerificationPolicy() *policiesv1alpha1.ImageVerificationPolicy { + return p.ImageVerificationPolicy +} + func (p *genericPolicy) GetAPIVersion() string { switch { case p.PolicyInterface != nil: @@ -62,6 +69,8 @@ func (p *genericPolicy) GetAPIVersion() string { return admissionregistrationv1alpha1.SchemeGroupVersion.String() case p.ValidatingPolicy != nil: return policiesv1alpha1.GroupVersion.String() + case p.ImageVerificationPolicy != nil: + return policiesv1alpha1.GroupVersion.String() } return "" } @@ -76,6 +85,8 @@ func (p *genericPolicy) GetKind() string { return "MutatingAdmissionPolicy" case p.ValidatingPolicy != nil: return "ValidatingPolicy" + case p.ImageVerificationPolicy != nil: + return "ImageVerificationPolicy" } return "" } @@ -115,3 +126,10 @@ func NewValidatingPolicy(pol *policiesv1alpha1.ValidatingPolicy) GenericPolicy { ValidatingPolicy: pol, } } + +func NewImageVerificationPolicy(pol *policiesv1alpha1.ImageVerificationPolicy) GenericPolicy { + return &genericPolicy{ + Object: pol, + ImageVerificationPolicy: pol, + } +} diff --git a/test/conformance/chainsaw/validating-policies/webhook-configuration/match-conditions/webhooks.yaml b/test/conformance/chainsaw/validating-policies/webhook-configuration/match-conditions/webhooks.yaml index 516a031f7b..61f7a3db7b 100644 --- a/test/conformance/chainsaw/validating-policies/webhook-configuration/match-conditions/webhooks.yaml +++ b/test/conformance/chainsaw/validating-policies/webhook-configuration/match-conditions/webhooks.yaml @@ -11,7 +11,7 @@ webhooks: service: name: kyverno-svc namespace: kyverno - path: /vpol/fail/finegrained/disallow-privilege-escalation + path: /policies/fail/finegrained/disallow-privilege-escalation port: 443 failurePolicy: Fail matchConditions: diff --git a/test/conformance/chainsaw/validating-policies/webhook-configuration/multiple/webhooks.yaml b/test/conformance/chainsaw/validating-policies/webhook-configuration/multiple/webhooks.yaml index 08acf53c44..7f4185acd4 100644 --- a/test/conformance/chainsaw/validating-policies/webhook-configuration/multiple/webhooks.yaml +++ b/test/conformance/chainsaw/validating-policies/webhook-configuration/multiple/webhooks.yaml @@ -11,7 +11,7 @@ webhooks: service: name: kyverno-svc namespace: kyverno - path: /vpol/fail + path: /policies/fail port: 443 failurePolicy: Fail matchPolicy: Equivalent diff --git a/test/conformance/chainsaw/validating-policies/webhook-configuration/single/webhooks.yaml b/test/conformance/chainsaw/validating-policies/webhook-configuration/single/webhooks.yaml index 31e8063c1c..1c9706a367 100644 --- a/test/conformance/chainsaw/validating-policies/webhook-configuration/single/webhooks.yaml +++ b/test/conformance/chainsaw/validating-policies/webhook-configuration/single/webhooks.yaml @@ -11,7 +11,7 @@ webhooks: service: name: kyverno-svc namespace: kyverno - path: /vpol/fail + path: /policies/fail port: 443 failurePolicy: Fail matchPolicy: Equivalent