diff --git a/pkg/config/dynamicconfig.go b/pkg/config/dynamicconfig.go index 9c754f5c4d..0177b5e2b6 100644 --- a/pkg/config/dynamicconfig.go +++ b/pkg/config/dynamicconfig.go @@ -23,26 +23,15 @@ var defaultExcludeGroupRole []string = []string{"system:serviceaccounts:kube-sys // ConfigData stores the configuration type ConfigData struct { - client kubernetes.Interface - // configMap Name - cmName string - // lock configuration - mux sync.RWMutex - // configuration data - filters []k8Resource - - // excludeGroupRole Role - excludeGroupRole []string - - //excludeUsername exclude username - excludeUsername []string - - //restrictDevelopmentUsername exclude dev username like minikube and kind + client kubernetes.Interface + cmName string + mux sync.RWMutex + filters []k8Resource + excludeGroupRole []string + excludeUsername []string restrictDevelopmentUsername []string - // hasynced - cmSycned cache.InformerSynced - - log logr.Logger + cmSycned cache.InformerSynced + log logr.Logger } // ToFilter checks if the given resource is set to be filtered in the configuration @@ -92,12 +81,14 @@ func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapI if cmNameEnv == "" { log.Info("ConfigMap name not defined in env:INIT_CONFIG: loading no default configuration") } + cd := ConfigData{ client: rclient, cmName: os.Getenv(cmNameEnv), cmSycned: cmInformer.Informer().HasSynced, log: log, } + cd.restrictDevelopmentUsername = []string{"minikube-user", "kubernetes-admin"} //TODO: this has been added to backward support command line arguments @@ -124,6 +115,7 @@ func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapI UpdateFunc: cd.updateCM, DeleteFunc: cd.deleteCM, }) + return &cd } diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 4e78cb6d24..316cad6092 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -1,17 +1,11 @@ package engine import ( - "time" - - "github.com/go-logr/logr" - kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1" - "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/variables" - "github.com/kyverno/kyverno/pkg/resourcecache" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/log" + "time" ) // Generate checks for validity of generate rule on the resource @@ -19,30 +13,62 @@ import ( // - the caller has to check the ruleResponse to determine whether the path exist // 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed func Generate(policyContext PolicyContext) (resp response.EngineResponse) { - policy := policyContext.Policy - new := policyContext.NewResource - old := policyContext.OldResource - admissionInfo := policyContext.AdmissionInfo - ctx := policyContext.Context - - resCache := policyContext.ResourceCache - jsonContext := policyContext.JSONContext - logger := log.Log.WithName("Generate").WithValues("policy", policy.Name, "kind", new.GetKind(), "namespace", new.GetNamespace(), "name", new.GetName()) - - return filterRules(policy, new, old, admissionInfo, ctx, logger, policyContext.ExcludeGroupRole, resCache, jsonContext) + return filterRules(policyContext) } -// filterRule checks if a rule matches the rule selection criteria. -// -func filterRule(rule kyverno.Rule, new, old unstructured.Unstructured, admissionInfo kyverno.RequestInfo, ctx context.EvalInterface, log logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCacheIface, jsonContext *context.Context) *response.RuleResponse { +func filterRules(policyContext PolicyContext) response.EngineResponse { + kind := policyContext.NewResource.GetKind() + name := policyContext.NewResource.GetName() + namespace := policyContext.NewResource.GetNamespace() + + resp := response.EngineResponse{ + PolicyResponse: response.PolicyResponse{ + Policy: policyContext.Policy.Name, + Resource: response.ResourceSpec{ + Kind: kind, + Name: name, + Namespace: namespace, + }, + }, + } + + if policyContext.ExcludeResourceFunc(kind, namespace, name) { + log.Log.WithName("Generate").Info("resource excluded", "kind", kind, "namespace", namespace, "name", name) + return resp + } + + for _, rule := range policyContext.Policy.Spec.Rules { + if ruleResp := filterRule(rule, policyContext); ruleResp != nil { + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) + } + } + + return resp +} + +func filterRule(rule kyverno.Rule, policyContext PolicyContext) *response.RuleResponse { if !rule.HasGenerate() { return nil } startTime := time.Now() - if err := MatchesResourceDescription(new, rule, admissionInfo, excludeGroupRole); err != nil { - if err := MatchesResourceDescription(old, rule, admissionInfo, excludeGroupRole); err == nil { + policy := policyContext.Policy + newResource := policyContext.NewResource + oldResource := policyContext.OldResource + admissionInfo := policyContext.AdmissionInfo + ctx := policyContext.Context + resCache := policyContext.ResourceCache + jsonContext := policyContext.JSONContext + excludeGroupRole := policyContext.ExcludeGroupRole + + logger := log.Log.WithName("Generate").WithValues("policy", policy.Name, + "kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName()) + + if err := MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole); err != nil { + + // if the oldResource matched, return "false" to delete GR for it + if err := MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole); err == nil { return &response.RuleResponse{ Name: rule.Name, Type: "Generation", @@ -52,12 +78,13 @@ func filterRule(rule kyverno.Rule, new, old unstructured.Unstructured, admission }, } } + return nil } // add configmap json data to context - if err := AddResourceToContext(log, rule.Context, resCache, jsonContext); err != nil { - log.V(4).Info("cannot add configmaps to context", "reason", err.Error()) + if err := AddResourceToContext(logger, rule.Context, resCache, jsonContext); err != nil { + logger.V(4).Info("cannot add configmaps to context", "reason", err.Error()) return nil } @@ -65,8 +92,8 @@ func filterRule(rule kyverno.Rule, new, old unstructured.Unstructured, admission copyConditions := copyConditions(rule.Conditions) // evaluate pre-conditions - if !variables.EvaluateConditions(log, ctx, copyConditions) { - log.V(4).Info("preconditions not satisfied, skipping rule", "rule", rule.Name) + if !variables.EvaluateConditions(logger, ctx, copyConditions) { + logger.V(4).Info("preconditions not satisfied, skipping rule", "rule", rule.Name) return nil } @@ -80,24 +107,3 @@ func filterRule(rule kyverno.Rule, new, old unstructured.Unstructured, admission }, } } - -func filterRules(policy kyverno.ClusterPolicy, new, old unstructured.Unstructured, admissionInfo kyverno.RequestInfo, ctx context.EvalInterface, log logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCacheIface, jsonContext *context.Context) response.EngineResponse { - resp := response.EngineResponse{ - PolicyResponse: response.PolicyResponse{ - Policy: policy.Name, - Resource: response.ResourceSpec{ - Kind: new.GetKind(), - Name: new.GetName(), - Namespace: new.GetNamespace(), - }, - }, - } - - for _, rule := range policy.Spec.Rules { - if ruleResp := filterRule(rule, new, old, admissionInfo, ctx, log, excludeGroupRole, resCache, jsonContext); ruleResp != nil { - resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) - } - } - - return resp -} diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index 764f318830..1b457230bf 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -10,23 +10,33 @@ import ( // PolicyContext contains the contexts for engine to process type PolicyContext struct { - // policy to be processed + + // Policy is the policy to be processed Policy kyverno.ClusterPolicy - // resource to be processed + + // NewResource is the resource to be processed NewResource unstructured.Unstructured - // old Resource - Update operations - OldResource unstructured.Unstructured + + // OldResource is the prior resource for an update, or nil + OldResource unstructured.Unstructured + + // AdmissionInfo contains the admission request information AdmissionInfo kyverno.RequestInfo + // Dynamic client - used by generate Client *client.Client + // Contexts to store resources Context context.EvalInterface + // Config handler ExcludeGroupRole []string - // ResourceCache provides listers to resources - // Currently Supports Configmap + ExcludeResourceFunc func(kind, namespace, name string) bool + + // ResourceCache provides listers to resources. Currently Supports Configmap ResourceCache resourcecache.ResourceCacheIface - // JSONContext ... + + // JSONContext is the variable context JSONContext *context.Context } diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index aca918fdf7..00bb616b5a 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -109,7 +109,7 @@ func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[strin // ClusterRoles []string // Subjects []rbacv1.Subject // To filter out the targeted resources with ResourceDescription, the check -// should be: AND across attibutes but an OR inside attributes that of type list +// should be: AND across attributes but an OR inside attributes that of type list // To filter out the targeted resources with UserInfo, the check // should be: OR (across & inside) attributes func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription, userInfo kyverno.UserInfo, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string) []error { diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go index 2401be0e9a..49f233297c 100644 --- a/pkg/generate/generate.go +++ b/pkg/generate/generate.go @@ -117,13 +117,14 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern } policyContext := engine.PolicyContext{ - NewResource: resource, - Policy: *policyObj, - Context: ctx, - AdmissionInfo: gr.Spec.Context.UserRequestInfo, - ExcludeGroupRole: c.Config.GetExcludeGroupRole(), - ResourceCache: c.resCache, - JSONContext: ctx, + NewResource: resource, + Policy: *policyObj, + Context: ctx, + AdmissionInfo: gr.Spec.Context.UserRequestInfo, + ExcludeGroupRole: c.Config.GetExcludeGroupRole(), + ExcludeResourceFunc: c.Config.ToFilter, + ResourceCache: c.resCache, + JSONContext: ctx, } // check if the policy still applies to the resource diff --git a/pkg/testrunner/scenario.go b/pkg/testrunner/scenario.go index 0272d0d852..572c96a474 100644 --- a/pkg/testrunner/scenario.go +++ b/pkg/testrunner/scenario.go @@ -157,6 +157,9 @@ func runTestCase(t *testing.T, tc scaseT) bool { Policy: *policy, Client: client, ExcludeGroupRole: []string{}, + ExcludeResourceFunc: func(s1, s2, s3 string) bool { + return false + }, } er = engine.Generate(policyContext) diff --git a/pkg/webhooks/generation.go b/pkg/webhooks/generation.go index fb7170dc93..e92f74c7e1 100644 --- a/pkg/webhooks/generation.go +++ b/pkg/webhooks/generation.go @@ -3,6 +3,7 @@ package webhooks import ( contextdefault "context" "fmt" + "github.com/go-logr/logr" "reflect" "sort" "strings" @@ -39,44 +40,30 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic } policyContext := engine.PolicyContext{ - NewResource: new, - OldResource: old, - AdmissionInfo: userRequestInfo, - Context: ctx, - ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), - ResourceCache: ws.resCache, - JSONContext: ctx, + NewResource: new, + OldResource: old, + AdmissionInfo: userRequestInfo, + Context: ctx, + ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), + ExcludeResourceFunc: ws.configHandler.ToFilter, + ResourceCache: ws.resCache, + JSONContext: ctx, } // engine.Generate returns a list of rules that are applicable on this resource var rules []response.RuleResponse + for _, policy := range policies { policyContext.Policy = *policy + engineResponse := engine.Generate(policyContext) for _, rule := range engineResponse.PolicyResponse.Rules { if !rule.Success { - ws.log.V(4).Info("querying all generate requests") - selector := labels.SelectorFromSet(labels.Set(map[string]string{ - "policyName": engineResponse.PolicyResponse.Policy, - "resourceName": engineResponse.PolicyResponse.Resource.Name, - "resourceKind": engineResponse.PolicyResponse.Resource.Kind, - "ResourceNamespace": engineResponse.PolicyResponse.Resource.Namespace, - })) - grList, err := ws.grLister.List(selector) - if err != nil { - logger.Error(err, "failed to get generate request for the resource", "kind", engineResponse.PolicyResponse.Resource.Kind, "name", engineResponse.PolicyResponse.Resource.Name, "namespace", engineResponse.PolicyResponse.Resource.Namespace) - continue - } - - for _, v := range grList { - err := ws.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(contextdefault.TODO(), v.GetName(), metav1.DeleteOptions{}) - if err != nil { - logger.Error(err, "failed to update gr") - } - } - } else { - rules = append(rules, rule) + ws.deleteGR(logger, engineResponse) + continue } + + rules = append(rules, rule) } if len(rules) > 0 { @@ -87,7 +74,6 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic resp: engineResponse, }) } - } // Adds Generate Request to a channel(queue size 1000) to generators @@ -102,6 +88,29 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic return } +func (ws *WebhookServer) deleteGR(logger logr.Logger, engineResponse response.EngineResponse) { + logger.V(4).Info("querying all generate requests") + selector := labels.SelectorFromSet(labels.Set(map[string]string{ + "policyName": engineResponse.PolicyResponse.Policy, + "resourceName": engineResponse.PolicyResponse.Resource.Name, + "resourceKind": engineResponse.PolicyResponse.Resource.Kind, + "ResourceNamespace": engineResponse.PolicyResponse.Resource.Namespace, + })) + + grList, err := ws.grLister.List(selector) + if err != nil { + logger.Error(err, "failed to get generate request for the resource", "kind", engineResponse.PolicyResponse.Resource.Kind, "name", engineResponse.PolicyResponse.Resource.Name, "namespace", engineResponse.PolicyResponse.Resource.Namespace) + + } + + for _, v := range grList { + err := ws.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(contextdefault.TODO(), v.GetName(), metav1.DeleteOptions{}) + if err != nil { + logger.Error(err, "failed to update gr") + } + } +} + func applyGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo, action v1beta1.Operation, engineResponses ...response.EngineResponse) (failedGenerateRequest []generateRequestResponse) { diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 1c743867e0..21d5c9fd74 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -38,12 +38,13 @@ func (ws *WebhookServer) HandleMutation( var patches [][]byte var engineResponses []response.EngineResponse policyContext := engine.PolicyContext{ - NewResource: resource, - AdmissionInfo: userRequestInfo, - Context: ctx, - ExcludeGroupRole: ws.configHandler.GetExcludeGroupRole(), - ResourceCache: ws.resCache, - JSONContext: ctx, + NewResource: resource, + AdmissionInfo: userRequestInfo, + Context: ctx, + ExcludeGroupRole: ws.configHandler.GetExcludeGroupRole(), + ExcludeResourceFunc: ws.configHandler.ToFilter, + ResourceCache: ws.resCache, + JSONContext: ctx, } if request.Operation == v1beta1.Update { diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index 30518fd3c9..727791e9d0 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -69,13 +69,14 @@ func HandleValidation( } policyContext := engine.PolicyContext{ - NewResource: newR, - OldResource: oldR, - Context: ctx, - AdmissionInfo: userRequestInfo, - ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), - ResourceCache: resCache, - JSONContext: ctx, + NewResource: newR, + OldResource: oldR, + Context: ctx, + AdmissionInfo: userRequestInfo, + ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), + ExcludeResourceFunc: dynamicConfig.ToFilter, + ResourceCache: resCache, + JSONContext: ctx, } var engineResponses []response.EngineResponse