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

refactor: add more functionnalities to engine interface (#6212)

* refactor: add more functionnalities to engine interface

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* exclude mechanism

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* polex

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix kuttl tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-02-06 06:49:47 +01:00 committed by GitHub
parent d0896cb57e
commit 6934c66a71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 255 additions and 238 deletions

View file

@ -229,6 +229,8 @@ func main() {
engine := engine.NewEngine(
configuration,
engine.LegacyContextLoaderFactory(rclient, configMapResolver),
// TODO: do we need exceptions here ?
nil,
)
// create non leader controllers
nonLeaderControllers := createNonLeaderControllers(

View file

@ -477,6 +477,7 @@ OuterLoop:
eng := engine.NewEngine(
cfg,
engine.LegacyContextLoaderFactory(registryclient.NewOrDie(), nil),
nil,
)
policyContext := engine.NewPolicyContextWithJsonContext(ctx).
WithPolicy(c.Policy).
@ -1080,6 +1081,7 @@ func initializeMockController(objects []runtime.Object) (*generate.GenerateContr
c := generate.NewGenerateControllerWithOnlyClient(client, engine.NewEngine(
config.NewDefaultConfiguration(),
engine.LegacyContextLoaderFactory(nil, nil),
nil,
))
return c, nil
}

View file

@ -355,9 +355,19 @@ func main() {
kubeKyvernoInformer.Apps().V1().Deployments(),
certRenewer,
)
var exceptionsLister engineapi.PolicyExceptionSelector
if enablePolicyException {
lister := kyvernoInformer.Kyverno().V2alpha1().PolicyExceptions().Lister()
if exceptionNamespace != "" {
exceptionsLister = lister.PolicyExceptions(exceptionNamespace)
} else {
exceptionsLister = lister
}
}
eng := engine.NewEngine(
configuration,
engine.LegacyContextLoaderFactory(rclient, configMapResolver),
exceptionsLister,
)
// create non leader controllers
nonLeaderControllers, nonLeaderBootstrap := createNonLeaderControllers(
@ -468,15 +478,6 @@ func main() {
dClient,
openApiManager,
)
var exceptionsLister engine.PolicyExceptionLister
if enablePolicyException {
lister := kyvernoInformer.Kyverno().V2alpha1().PolicyExceptions().Lister()
if exceptionNamespace != "" {
exceptionsLister = lister.PolicyExceptions(exceptionNamespace)
} else {
exceptionsLister = lister
}
}
resourceHandlers := webhooksresource.NewHandlers(
eng,
dClient,
@ -489,7 +490,6 @@ func main() {
kubeInformer.Rbac().V1().RoleBindings().Lister(),
kubeInformer.Rbac().V1().ClusterRoleBindings().Lister(),
kyvernoInformer.Kyverno().V1beta1().UpdateRequests().Lister().UpdateRequests(config.KyvernoNamespace()),
exceptionsLister,
urgen,
eventGenerator,
openApiManager,

View file

@ -86,7 +86,6 @@ func createReportControllers(
var ctrls []internal.Controller
var warmups []func(context.Context) error
kyvernoV1 := kyvernoInformer.Kyverno().V1()
kyvernoV2Alpha1 := kyvernoInformer.Kyverno().V2alpha1()
if backgroundScan || admissionReports {
resourceReportController := resourcereportcontroller.NewController(
client,
@ -136,7 +135,6 @@ func createReportControllers(
kyvernoV1.Policies(),
kyvernoV1.ClusterPolicies(),
kubeInformer.Core().V1().Namespaces(),
kyvernoV2Alpha1.PolicyExceptions().Lister(),
resourceReportController,
configMapResolver,
backgroundScanInterval,
@ -206,6 +204,8 @@ func main() {
backgroundScanWorkers int
backgroundScanInterval time.Duration
maxQueuedEvents int
enablePolicyException bool
exceptionNamespace string
)
flagset := flag.NewFlagSet("reports-controller", flag.ExitOnError)
flagset.DurationVar(&leaderElectionRetryPeriod, "leaderElectionRetryPeriod", leaderelection.DefaultRetryPeriod, "Configure leader election retry period.")
@ -218,6 +218,8 @@ func main() {
flagset.IntVar(&backgroundScanWorkers, "backgroundScanWorkers", backgroundscancontroller.Workers, "Configure the number of background scan workers.")
flagset.DurationVar(&backgroundScanInterval, "backgroundScanInterval", time.Hour, "Configure background scan interval.")
flagset.IntVar(&maxQueuedEvents, "maxQueuedEvents", 1000, "Maximum events to be queued.")
flagset.StringVar(&exceptionNamespace, "exceptionNamespace", "", "Configure the namespace to accept PolicyExceptions.")
flagset.BoolVar(&enablePolicyException, "enablePolicyException", false, "Enable PolicyException feature.")
// config
appConfig := internal.NewConfiguration(
internal.WithProfiling(),
@ -294,6 +296,15 @@ func main() {
maxQueuedEvents,
logging.WithName("EventGenerator"),
)
var exceptionsLister engineapi.PolicyExceptionSelector
if enablePolicyException {
lister := kyvernoInformer.Kyverno().V2alpha1().PolicyExceptions().Lister()
if exceptionNamespace != "" {
exceptionsLister = lister.PolicyExceptions(exceptionNamespace)
} else {
exceptionsLister = lister
}
}
// start informers and wait for cache sync
if !internal.StartInformersAndWaitForCacheSync(ctx, kyvernoInformer, kubeKyvernoInformer, cacheInformer) {
logger.Error(errors.New("failed to wait for cache sync"), "failed to wait for cache sync")
@ -304,6 +315,7 @@ func main() {
eng := engine.NewEngine(
configuration,
engine.LegacyContextLoaderFactory(rclient, configMapResolver),
exceptionsLister,
)
// setup leader election
le, err := leaderelection.New(

View file

@ -81,7 +81,6 @@ func NewBackgroundContext(dclient dclient.Interface, ur *kyvernov1beta1.UpdateRe
WithNewResource(*trigger).
WithOldResource(old).
WithAdmissionInfo(ur.Spec.Context.UserRequestInfo).
WithConfiguration(cfg).
WithNamespaceLabels(namespaceLabels).
WithClient(dclient)

View file

@ -17,7 +17,6 @@ import (
"github.com/kyverno/kyverno/pkg/controllers"
"github.com/kyverno/kyverno/pkg/controllers/report/resource"
"github.com/kyverno/kyverno/pkg/controllers/report/utils"
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/registryclient"
@ -57,7 +56,6 @@ type controller struct {
bgscanrLister cache.GenericLister
cbgscanrLister cache.GenericLister
nsLister corev1listers.NamespaceLister
polexLister engine.PolicyExceptionLister
// queue
queue workqueue.RateLimitingInterface
@ -81,7 +79,6 @@ func NewController(
polInformer kyvernov1informers.PolicyInformer,
cpolInformer kyvernov1informers.ClusterPolicyInformer,
nsInformer corev1informers.NamespaceInformer,
polexLister engine.PolicyExceptionLister,
metadataCache resource.MetadataCache,
informerCacheResolvers engineapi.ConfigmapResolver,
forceDelay time.Duration,
@ -101,7 +98,6 @@ func NewController(
bgscanrLister: bgscanr.Lister(),
cbgscanrLister: cbgscanr.Lister(),
nsLister: nsInformer.Lister(),
polexLister: polexLister,
queue: queue,
metadataCache: metadataCache,
informerCacheResolvers: informerCacheResolvers,
@ -312,7 +308,7 @@ func (c *controller) reconcileReport(
// calculate necessary results
for _, policy := range backgroundPolicies {
if full || actual[reportutils.PolicyLabel(policy)] != policy.GetResourceVersion() {
scanner := utils.NewScanner(logger, c.engine, c.client, c.rclient, c.polexLister, c.config)
scanner := utils.NewScanner(logger, c.engine, c.client, c.rclient, c.config)
for _, result := range scanner.ScanResource(ctx, *target, nsLabels, policy) {
if result.Error != nil {
return result.Error

View file

@ -16,13 +16,11 @@ import (
)
type scanner struct {
logger logr.Logger
engine engineapi.Engine
client dclient.Interface
rclient registryclient.Client
polexLister engine.PolicyExceptionLister
excludeGroupRole []string
config config.Configuration
logger logr.Logger
engine engineapi.Engine
client dclient.Interface
rclient registryclient.Client
config config.Configuration
}
type ScanResult struct {
@ -39,18 +37,14 @@ func NewScanner(
engine engineapi.Engine,
client dclient.Interface,
rclient registryclient.Client,
polexLister engine.PolicyExceptionLister,
config config.Configuration,
excludeGroupRole ...string,
) Scanner {
return &scanner{
logger: logger,
engine: engine,
client: client,
rclient: rclient,
polexLister: polexLister,
config: config,
excludeGroupRole: excludeGroupRole,
logger: logger,
engine: engine,
client: client,
rclient: rclient,
config: config,
}
}
@ -99,9 +93,7 @@ func (s *scanner) validateResource(ctx context.Context, resource unstructured.Un
WithNewResource(resource).
WithPolicy(policy).
WithClient(s.client).
WithNamespaceLabels(nsLabels).
WithExcludeGroupRole(s.excludeGroupRole...).
WithExceptions(s.polexLister)
WithNamespaceLabels(nsLabels)
return s.engine.Validate(ctx, policyCtx), nil
}
@ -123,9 +115,7 @@ func (s *scanner) validateImages(ctx context.Context, resource unstructured.Unst
WithNewResource(resource).
WithPolicy(policy).
WithClient(s.client).
WithNamespaceLabels(nsLabels).
WithExcludeGroupRole(s.excludeGroupRole...).
WithExceptions(s.polexLister)
WithNamespaceLabels(nsLabels)
response, _ := s.engine.VerifyAndPatchImages(ctx, s.rclient, policyCtx)
if len(response.PolicyResponse.Rules) > 0 {
s.logger.Info("validateImages", "policy", policy, "response", response)

View file

@ -4,9 +4,19 @@ import (
"context"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/registryclient"
"k8s.io/apimachinery/pkg/labels"
)
type NamespacedResourceSelector[T any] interface {
// List selects resources based on label selector.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []T, err error)
}
type PolicyExceptionSelector = NamespacedResourceSelector[*kyvernov2alpha1.PolicyException]
type Engine interface {
// Validate applies validation rules from policy on the resource
Validate(

View file

@ -3,7 +3,6 @@ package api
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -26,7 +25,6 @@ type PolicyContext interface {
NamespaceLabels() map[string]string
SubResource() string
SubresourcesInPolicy() []SubResource
ExcludeGroupRole() []string
AdmissionOperation() bool
RequestResource() metav1.GroupVersionResource
Element() unstructured.Unstructured
@ -35,7 +33,4 @@ type PolicyContext interface {
JSONContext() enginecontext.Interface
Client() dclient.Interface
Copy() PolicyContext
FindExceptions(rule string) ([]*kyvernov2alpha1.PolicyException, error)
ExcludeResourceFunc() ExcludeFunc
}

View file

@ -6,6 +6,7 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
@ -19,16 +20,20 @@ import (
// 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed
func doApplyBackgroundChecks(
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
cfg config.Configuration,
) (resp *engineapi.EngineResponse) {
policyStartTime := time.Now()
return filterRules(contextLoader, policyContext, policyStartTime)
return filterRules(contextLoader, selector, policyContext, policyStartTime, cfg)
}
func filterRules(
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
startTime time.Time,
cfg config.Configuration,
) *engineapi.EngineResponse {
newResource := policyContext.NewResource()
policy := policyContext.Policy()
@ -56,14 +61,14 @@ func filterRules(
},
}
if policyContext.ExcludeResourceFunc()(kind, namespace, name) {
if cfg.ToFilter(kind, namespace, name) {
logging.WithName("ApplyBackgroundChecks").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name)
return resp
}
applyRules := policy.GetSpec().GetApplyRules()
for _, rule := range autogen.ComputeRules(policy) {
if ruleResp := filterRule(contextLoader, rule, policyContext); ruleResp != nil {
if ruleResp := filterRule(contextLoader, selector, rule, policyContext, cfg); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
if applyRules == kyvernov1.ApplyOne && ruleResp.Status != engineapi.RuleStatusSkip {
break
@ -76,8 +81,10 @@ func filterRules(
func filterRule(
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
rule kyvernov1.Rule,
policyContext engineapi.PolicyContext,
cfg config.Configuration,
) *engineapi.RuleResponse {
if !rule.HasGenerate() && !rule.IsMutateExisting() {
return nil
@ -89,7 +96,7 @@ func filterRule(
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext)
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(policyContext, &rule, subresourceGVKToAPIResource, logger)
ruleResp := hasPolicyExceptions(logger, selector, policyContext, &rule, subresourceGVKToAPIResource, cfg)
if ruleResp != nil {
return ruleResp
}
@ -106,7 +113,7 @@ func filterRule(
oldResource := policyContext.OldResource()
admissionInfo := policyContext.AdmissionInfo()
ctx := policyContext.JSONContext()
excludeGroupRole := policyContext.ExcludeGroupRole()
excludeGroupRole := cfg.GetExcludeGroupRole()
namespaceLabels := policyContext.NamespaceLabels()
logger = logging.WithName(string(ruleType)).WithValues("policy", policy.GetName(),

View file

@ -10,17 +10,20 @@ import (
)
type engine struct {
configuration config.Configuration
contextLoader engineapi.ContextLoaderFactory
configuration config.Configuration
contextLoader engineapi.ContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
}
func NewEngine(
configuration config.Configuration,
contextLoader engineapi.ContextLoaderFactory,
exceptionSelector engineapi.PolicyExceptionSelector,
) engineapi.Engine {
return &engine{
configuration: configuration,
contextLoader: contextLoader,
configuration: configuration,
contextLoader: contextLoader,
exceptionSelector: exceptionSelector,
}
}
@ -28,14 +31,14 @@ func (e *engine) Validate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) *engineapi.EngineResponse {
return doValidate(ctx, e.contextLoader, policyContext, e.configuration)
return doValidate(ctx, e.contextLoader, e.exceptionSelector, policyContext, e.configuration)
}
func (e *engine) Mutate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) *engineapi.EngineResponse {
return doMutate(ctx, e.contextLoader, policyContext)
return doMutate(ctx, e.contextLoader, e.exceptionSelector, policyContext, e.configuration)
}
func (e *engine) VerifyAndPatchImages(
@ -43,20 +46,20 @@ func (e *engine) VerifyAndPatchImages(
rclient registryclient.Client,
policyContext engineapi.PolicyContext,
) (*engineapi.EngineResponse, *engineapi.ImageVerificationMetadata) {
return doVerifyAndPatchImages(ctx, e.contextLoader, rclient, policyContext, e.configuration)
return doVerifyAndPatchImages(ctx, e.contextLoader, e.exceptionSelector, rclient, policyContext, e.configuration)
}
func (e *engine) ApplyBackgroundChecks(
policyContext engineapi.PolicyContext,
) *engineapi.EngineResponse {
return doApplyBackgroundChecks(e.contextLoader, policyContext)
return doApplyBackgroundChecks(e.contextLoader, e.exceptionSelector, policyContext, e.configuration)
}
func (e *engine) GenerateResponse(
policyContext engineapi.PolicyContext,
gr kyvernov1beta1.UpdateRequest,
) *engineapi.EngineResponse {
return doGenerateResponse(e.contextLoader, policyContext, gr)
return doGenerateResponse(e.contextLoader, e.exceptionSelector, policyContext, gr, e.configuration)
}
func (e *engine) ContextLoader(

103
pkg/engine/exceptions.go Normal file
View file

@ -0,0 +1,103 @@
package engine
import (
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
matched "github.com/kyverno/kyverno/pkg/utils/match"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
func findExceptions(
selector engineapi.PolicyExceptionSelector,
policy kyvernov1.PolicyInterface,
rule string,
) ([]*kyvernov2alpha1.PolicyException, error) {
if selector == nil {
return nil, nil
}
polexs, err := selector.List(labels.Everything())
if err != nil {
return nil, err
}
var result []*kyvernov2alpha1.PolicyException
policyName, err := cache.MetaNamespaceKeyFunc(policy)
if err != nil {
return nil, fmt.Errorf("failed to compute policy key: %w", err)
}
for _, polex := range polexs {
if polex.Contains(policyName, rule) {
result = append(result, polex)
}
}
return result, nil
}
// matchesException checks if an exception applies to the resource being admitted
func matchesException(
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
rule *kyvernov1.Rule,
subresourceGVKToAPIResource map[string]*metav1.APIResource,
cfg config.Configuration,
) (*kyvernov2alpha1.PolicyException, error) {
candidates, err := findExceptions(selector, policyContext.Policy(), rule.Name)
if err != nil {
return nil, err
}
for _, candidate := range candidates {
err := matched.CheckMatchesResources(
policyContext.NewResource(),
candidate.Spec.Match,
policyContext.NamespaceLabels(),
subresourceGVKToAPIResource,
policyContext.SubResource(),
policyContext.AdmissionInfo(),
cfg.GetExcludeGroupRole(),
)
// if there's no error it means a match
if err == nil {
return candidate, nil
}
}
return nil, nil
}
// hasPolicyExceptions returns nil when there are no matching exceptions.
// A rule response is returned when an exception is matched, or there is an error.
func hasPolicyExceptions(
log logr.Logger,
selector engineapi.PolicyExceptionSelector,
ctx engineapi.PolicyContext,
rule *kyvernov1.Rule,
subresourceGVKToAPIResource map[string]*metav1.APIResource,
cfg config.Configuration,
) *engineapi.RuleResponse {
// if matches, check if there is a corresponding policy exception
exception, err := matchesException(selector, ctx, rule, subresourceGVKToAPIResource, cfg)
// if we found an exception
if err == nil && exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
log.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return &engineapi.RuleResponse{
Name: rule.Name,
Message: "failed to find matched exception " + key,
Status: engineapi.RuleStatusError,
}
}
log.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return &engineapi.RuleResponse{
Name: rule.Name,
Message: "rule skipped due to policy exception " + key,
Status: engineapi.RuleStatusSkip,
}
}
return nil
}

View file

@ -5,6 +5,7 @@ import (
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/logging"
"k8s.io/client-go/tools/cache"
@ -13,18 +14,22 @@ import (
// GenerateResponse checks for validity of generate rule on the resource
func doGenerateResponse(
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
gr kyvernov1beta1.UpdateRequest,
cfg config.Configuration,
) (resp *engineapi.EngineResponse) {
policyStartTime := time.Now()
return filterGenerateRules(contextLoader, policyContext, gr.Spec.Policy, policyStartTime)
return filterGenerateRules(contextLoader, selector, policyContext, gr.Spec.Policy, policyStartTime, cfg)
}
func filterGenerateRules(
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
policyNameKey string,
startTime time.Time,
cfg config.Configuration,
) *engineapi.EngineResponse {
newResource := policyContext.NewResource()
kind := newResource.GetKind()
@ -54,13 +59,13 @@ func filterGenerateRules(
},
},
}
if policyContext.ExcludeResourceFunc()(kind, namespace, name) {
if cfg.ToFilter(kind, namespace, name) {
logging.WithName("Generate").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name)
return resp
}
for _, rule := range autogen.ComputeRules(policyContext.Policy()) {
if ruleResp := filterRule(contextLoader, rule, policyContext); ruleResp != nil {
if ruleResp := filterRule(contextLoader, selector, rule, policyContext, cfg); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
}
}

View file

@ -32,6 +32,7 @@ import (
func doVerifyAndPatchImages(
ctx context.Context,
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
rclient registryclient.Client,
policyContext engineapi.PolicyContext,
cfg config.Configuration,
@ -73,12 +74,12 @@ func doVerifyAndPatchImages(
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext)
if !matches(logger, rule, policyContext, subresourceGVKToAPIResource) {
if !matches(logger, rule, policyContext, subresourceGVKToAPIResource, cfg) {
return
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(policyContext, rule, subresourceGVKToAPIResource, logger)
ruleResp := hasPolicyExceptions(logger, selector, policyContext, rule, subresourceGVKToAPIResource, cfg)
if ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
return

View file

@ -170,11 +170,11 @@ func testVerifyAndPatchImages(
return doVerifyAndPatchImages(
ctx,
LegacyContextLoaderFactory(rclient, cmResolver),
nil,
rclient,
pContext,
cfg,
)
}
func Test_CosignMockAttest(t *testing.T) {

View file

@ -10,6 +10,7 @@ import (
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/mutate"
"github.com/kyverno/kyverno/pkg/logging"
@ -24,7 +25,9 @@ import (
func doMutate(
ctx context.Context,
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
cfg config.Configuration,
) (resp *engineapi.EngineResponse) {
startTime := time.Now()
policy := policyContext.Policy()
@ -62,8 +65,8 @@ func doMutate(
func(ctx context.Context, span trace.Span) {
logger := logger.WithValues("rule", rule.Name)
var excludeResource []string
if len(policyContext.ExcludeGroupRole()) > 0 {
excludeResource = policyContext.ExcludeGroupRole()
if len(cfg.GetExcludeGroupRole()) > 0 {
excludeResource = cfg.GetExcludeGroupRole()
}
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
@ -75,7 +78,7 @@ func doMutate(
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(policyContext, &computeRules[i], subresourceGVKToAPIResource, logger)
ruleResp := hasPolicyExceptions(logger, selector, policyContext, &computeRules[i], subresourceGVKToAPIResource, cfg)
if ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
return

View file

@ -10,6 +10,7 @@ import (
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
client "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/registryclient"
@ -28,7 +29,9 @@ func testMutate(
return doMutate(
ctx,
LegacyContextLoaderFactory(rclient, nil),
nil,
pContext,
config.NewDefaultConfiguration(),
)
}

View file

@ -5,7 +5,6 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -14,16 +13,8 @@ import (
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
type PolicyExceptionLister interface {
// List lists all PolicyExceptions in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*kyvernov2alpha1.PolicyException, err error)
}
// PolicyContext contains the contexts for engine to process
type PolicyContext struct {
// policy is the policy to be processed
@ -54,11 +45,6 @@ type PolicyContext struct {
// Dynamic client - used for api lookups
client dclient.Interface
// Config handler
excludeGroupRole []string
excludeResourceFunc engineapi.ExcludeFunc
// jsonContext is the variable context
jsonContext enginectx.Interface
@ -75,9 +61,6 @@ type PolicyContext struct {
// This is used to determine if a resource is a subresource. It is only used when the policy context is populated
// by kyverno CLI. In all other cases when connected to a cluster, this is empty.
subresourcesInPolicy []engineapi.SubResource
// peLister list all policy exceptions
peLister PolicyExceptionLister
}
// engineapi.PolicyContext interface
@ -110,10 +93,6 @@ func (c *PolicyContext) SubresourcesInPolicy() []engineapi.SubResource {
return c.subresourcesInPolicy
}
func (c *PolicyContext) ExcludeGroupRole() []string {
return c.excludeGroupRole
}
func (c *PolicyContext) AdmissionOperation() bool {
return c.admissionOperation
}
@ -142,31 +121,6 @@ func (c PolicyContext) Copy() engineapi.PolicyContext {
return c.copy()
}
func (c *PolicyContext) FindExceptions(rule string) ([]*kyvernov2alpha1.PolicyException, error) {
if c.peLister == nil {
return nil, nil
}
polexs, err := c.peLister.List(labels.Everything())
if err != nil {
return nil, err
}
var result []*kyvernov2alpha1.PolicyException
policyName, err := cache.MetaNamespaceKeyFunc(c.policy)
if err != nil {
return nil, fmt.Errorf("failed to compute policy key: %w", err)
}
for _, polex := range polexs {
if polex.Contains(policyName, rule) {
result = append(result, polex)
}
}
return result, nil
}
func (c *PolicyContext) ExcludeResourceFunc() engineapi.ExcludeFunc {
return c.excludeResourceFunc
}
// Mutators
func (c *PolicyContext) WithPolicy(policy kyvernov1.PolicyInterface) *PolicyContext {
@ -215,23 +169,7 @@ func (c *PolicyContext) WithClient(client dclient.Interface) *PolicyContext {
return copy
}
func (c *PolicyContext) WithExcludeGroupRole(excludeGroupRole ...string) *PolicyContext {
copy := c.copy()
copy.excludeGroupRole = excludeGroupRole
return copy
}
func (c *PolicyContext) WithExcludeResourceFunc(excludeResourceFunc engineapi.ExcludeFunc) *PolicyContext {
copy := c.copy()
copy.excludeResourceFunc = excludeResourceFunc
return copy
}
func (c *PolicyContext) WithConfiguration(configuration config.Configuration) *PolicyContext {
return c.WithExcludeResourceFunc(configuration.ToFilter).WithExcludeGroupRole(configuration.GetExcludeGroupRole()...)
}
func (c *PolicyContext) WithAdmissionOperation(admissionOperation bool) *PolicyContext {
func (c *PolicyContext) withAdmissionOperation(admissionOperation bool) *PolicyContext {
copy := c.copy()
copy.admissionOperation = admissionOperation
return copy
@ -249,24 +187,15 @@ func (c *PolicyContext) WithSubresourcesInPolicy(subresourcesInPolicy []engineap
return copy
}
func (c *PolicyContext) WithExceptions(peLister PolicyExceptionLister) *PolicyContext {
copy := c.copy()
copy.peLister = peLister
return copy
}
func (c PolicyContext) copy() *PolicyContext {
return &c
}
// Constructors
func NewPolicyContextWithJsonContext(jsonContext enginectx.Interface) *PolicyContext {
return &PolicyContext{
jsonContext: jsonContext,
excludeGroupRole: []string{},
excludeResourceFunc: func(string, string, string) bool {
return false
},
jsonContext: jsonContext,
}
}
@ -279,7 +208,6 @@ func NewPolicyContextFromAdmissionRequest(
admissionInfo kyvernov1beta1.RequestInfo,
configuration config.Configuration,
client dclient.Interface,
polexLister PolicyExceptionLister,
) (*PolicyContext, error) {
ctx, err := newVariablesContext(request, &admissionInfo)
if err != nil {
@ -297,12 +225,10 @@ func NewPolicyContextFromAdmissionRequest(
WithNewResource(newResource).
WithOldResource(oldResource).
WithAdmissionInfo(admissionInfo).
WithConfiguration(configuration).
WithClient(client).
WithAdmissionOperation(true).
withAdmissionOperation(true).
WithRequestResource(*requestResource).
WithSubresource(request.SubResource).
WithExceptions(polexLister)
WithSubresource(request.SubResource)
return policyContext, nil
}

View file

@ -11,7 +11,6 @@ import (
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -23,7 +22,6 @@ import (
"github.com/kyverno/kyverno/pkg/tracing"
"github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
matched "github.com/kyverno/kyverno/pkg/utils/match"
"go.opentelemetry.io/otel/trace"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
@ -31,12 +29,12 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/tools/cache"
)
func doValidate(
ctx context.Context,
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
policyContext engineapi.PolicyContext,
cfg config.Configuration,
) (resp *engineapi.EngineResponse) {
@ -50,7 +48,7 @@ func doValidate(
logger.V(4).Info("finished policy processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
}()
resp = validateResource(ctx, contextLoader, logger, policyContext, cfg)
resp = validateResource(ctx, contextLoader, selector, logger, policyContext, cfg)
resp.NamespaceLabels = policyContext.NamespaceLabels()
return
}
@ -104,6 +102,7 @@ func buildResponse(ctx engineapi.PolicyContext, resp *engineapi.EngineResponse,
func validateResource(
ctx context.Context,
contextLoader engineapi.ContextLoaderFactory,
selector engineapi.PolicyExceptionSelector,
log logr.Logger,
enginectx engineapi.PolicyContext,
cfg config.Configuration,
@ -149,11 +148,11 @@ func validateResource(
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, enginectx)
if !matches(log, rule, enginectx, subresourceGVKToAPIResource) {
if !matches(log, rule, enginectx, subresourceGVKToAPIResource, cfg) {
return nil
}
// check if there is a corresponding policy exception
ruleResp := hasPolicyExceptions(enginectx, rule, subresourceGVKToAPIResource, log)
ruleResp := hasPolicyExceptions(log, selector, enginectx, rule, subresourceGVKToAPIResource, cfg)
if ruleResp != nil {
return ruleResp
}
@ -595,14 +594,20 @@ func isEmptyUnstructured(u *unstructured.Unstructured) bool {
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(logger logr.Logger, rule *kyvernov1.Rule, ctx engineapi.PolicyContext, subresourceGVKToAPIResource map[string]*metav1.APIResource) bool {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.NewResource(), *rule, ctx.AdmissionInfo(), ctx.ExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
func matches(
logger logr.Logger,
rule *kyvernov1.Rule,
ctx engineapi.PolicyContext,
subresourceGVKToAPIResource map[string]*metav1.APIResource,
cfg config.Configuration,
) bool {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.NewResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
if err == nil {
return true
}
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.OldResource(), *rule, ctx.AdmissionInfo(), ctx.ExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.OldResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
if err == nil {
return true
}
@ -790,57 +795,3 @@ func (v *validator) substituteDeny() error {
v.deny = i.(*kyvernov1.Deny)
return nil
}
// matchesException checks if an exception applies to the resource being admitted
func matchesException(
policyContext engineapi.PolicyContext,
rule *kyvernov1.Rule,
subresourceGVKToAPIResource map[string]*metav1.APIResource,
) (*kyvernov2alpha1.PolicyException, error) {
candidates, err := policyContext.FindExceptions(rule.Name)
if err != nil {
return nil, err
}
for _, candidate := range candidates {
err := matched.CheckMatchesResources(
policyContext.NewResource(),
candidate.Spec.Match,
policyContext.NamespaceLabels(),
subresourceGVKToAPIResource,
policyContext.SubResource(),
policyContext.AdmissionInfo(),
policyContext.ExcludeGroupRole(),
)
// if there's no error it means a match
if err == nil {
return candidate, nil
}
}
return nil, nil
}
// hasPolicyExceptions returns nil when there are no matching exceptions.
// A rule response is returned when an exception is matched, or there is an error.
func hasPolicyExceptions(ctx engineapi.PolicyContext, rule *kyvernov1.Rule, subresourceGVKToAPIResource map[string]*metav1.APIResource, log logr.Logger) *engineapi.RuleResponse {
// if matches, check if there is a corresponding policy exception
exception, err := matchesException(ctx, rule, subresourceGVKToAPIResource)
// if we found an exception
if err == nil && exception != nil {
key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil {
log.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return &engineapi.RuleResponse{
Name: rule.Name,
Message: "failed to find matched exception " + key,
Status: engineapi.RuleStatusError,
}
}
log.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return &engineapi.RuleResponse{
Name: rule.Name,
Message: "rule skipped due to policy exception " + key,
Status: engineapi.RuleStatusSkip,
}
}
return nil
}

View file

@ -23,6 +23,7 @@ func testValidate(ctx context.Context, rclient registryclient.Client, pContext *
return doValidate(
ctx,
LegacyContextLoaderFactory(rclient, nil),
nil,
pContext,
cfg,
)

View file

@ -146,7 +146,11 @@ func runTestCase(t *testing.T, tc TestCase) bool {
}
policyContext := engine.NewPolicyContext().WithPolicy(policy).WithNewResource(*resource)
eng := engine.NewEngine(config.NewDefaultConfiguration(), engine.LegacyContextLoaderFactory(registryclient.NewOrDie(), nil))
eng := engine.NewEngine(
config.NewDefaultConfiguration(),
engine.LegacyContextLoaderFactory(registryclient.NewOrDie(), nil),
nil,
)
er := eng.Mutate(
context.TODO(),
policyContext,

View file

@ -54,8 +54,12 @@ func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhook
urGenerator: updaterequest.NewFake(),
eventGen: event.NewFake(),
openApiManager: openapi.NewFake(),
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, dclient, rbLister, crbLister, peLister),
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, dclient, rbLister, crbLister),
urUpdater: webhookutils.NewUpdateRequestUpdater(kyvernoclient, urLister),
engine: engine.NewEngine(configuration, engine.LegacyContextLoaderFactory(rclient, configMapResolver)),
engine: engine.NewEngine(
configuration,
engine.LegacyContextLoaderFactory(rclient, configMapResolver),
peLister,
),
}
}

View file

@ -12,7 +12,6 @@ import (
kyvernov1beta1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/event"
@ -51,11 +50,10 @@ type handlers struct {
pCache policycache.Cache
// listers
nsLister corev1listers.NamespaceLister
rbLister rbacv1listers.RoleBindingLister
crbLister rbacv1listers.ClusterRoleBindingLister
urLister kyvernov1beta1listers.UpdateRequestNamespaceLister
polexLister engine.PolicyExceptionLister
nsLister corev1listers.NamespaceLister
rbLister rbacv1listers.RoleBindingLister
crbLister rbacv1listers.ClusterRoleBindingLister
urLister kyvernov1beta1listers.UpdateRequestNamespaceLister
urGenerator webhookgenerate.Generator
eventGen event.Interface
@ -78,7 +76,6 @@ func NewHandlers(
rbLister rbacv1listers.RoleBindingLister,
crbLister rbacv1listers.ClusterRoleBindingLister,
urLister kyvernov1beta1listers.UpdateRequestNamespaceLister,
polexLister engine.PolicyExceptionLister,
urGenerator webhookgenerate.Generator,
eventGen event.Interface,
openApiManager openapi.ValidateInterface,
@ -96,11 +93,10 @@ func NewHandlers(
rbLister: rbLister,
crbLister: crbLister,
urLister: urLister,
polexLister: polexLister,
urGenerator: urGenerator,
eventGen: eventGen,
openApiManager: openApiManager,
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, client, rbLister, crbLister, polexLister),
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, client, rbLister, crbLister),
urUpdater: webhookutils.NewUpdateRequestUpdater(kyvernoClient, urLister),
admissionReports: admissionReports,
}

View file

@ -1052,6 +1052,7 @@ func TestValidate_failure_action_overrides(t *testing.T) {
eng := engine.NewEngine(
config.NewDefaultConfiguration(),
engine.LegacyContextLoaderFactory(registryclient.NewOrDie(), nil),
nil,
)
for i, tc := range testcases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
@ -1128,6 +1129,7 @@ func Test_RuleSelector(t *testing.T) {
eng := engine.NewEngine(
config.NewDefaultConfiguration(),
engine.LegacyContextLoaderFactory(registryclient.NewOrDie(), nil),
nil,
)
resp := eng.Validate(
context.TODO(),

View file

@ -21,7 +21,6 @@ type policyContextBuilder struct {
client dclient.Interface
rbLister rbacv1listers.RoleBindingLister
crbLister rbacv1listers.ClusterRoleBindingLister
polexLister engine.PolicyExceptionLister
}
func NewPolicyContextBuilder(
@ -29,14 +28,12 @@ func NewPolicyContextBuilder(
client dclient.Interface,
rbLister rbacv1listers.RoleBindingLister,
crbLister rbacv1listers.ClusterRoleBindingLister,
polexLister engine.PolicyExceptionLister,
) PolicyContextBuilder {
return &policyContextBuilder{
configuration: configuration,
client: client,
rbLister: rbLister,
crbLister: crbLister,
polexLister: polexLister,
}
}
@ -50,5 +47,5 @@ func (b *policyContextBuilder) Build(request *admissionv1.AdmissionRequest) (*en
userRequestInfo.Roles = roles
userRequestInfo.ClusterRoles = clusterRoles
}
return engine.NewPolicyContextFromAdmissionRequest(request, userRequestInfo, b.configuration, b.client, b.polexLister)
return engine.NewPolicyContextFromAdmissionRequest(request, userRequestInfo, b.configuration, b.client)
}

View file

@ -15,19 +15,23 @@ cleanupController:
resources:
- pods
reportsController:
extraArgs:
- --enablePolicyException
backgroundController:
rbac:
clusterRole:
extraResources:
- apiGroups:
- '*'
resources:
- configmaps
- secrets
- roles
- rolebindings
- limitranges
- namespaces
- nodes
- nodes/status
- pods
- apiGroups:
- '*'
resources:
- configmaps
- secrets
- roles
- rolebindings
- limitranges
- namespaces
- nodes
- nodes/status
- pods

View file

@ -2,6 +2,7 @@ apiVersion: kyverno.io/v2alpha1
kind: PolicyException
metadata:
name: mynewpolex
namespace: kyverno
spec:
exceptions:
- policyName: require-labels