package policy import ( "context" "fmt" "github.com/kyverno/kyverno/api/kyverno" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/background/common" backgroundcommon "github.com/kyverno/kyverno/pkg/background/common" generateutils "github.com/kyverno/kyverno/pkg/background/generate" "github.com/kyverno/kyverno/pkg/config" datautils "github.com/kyverno/kyverno/pkg/utils/data" engineutils "github.com/kyverno/kyverno/pkg/utils/engine" "go.uber.org/multierr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) func (pc *policyController) handleGenerate(policyKey string, policy kyvernov1.PolicyInterface) error { logger := pc.log.WithName("handleGenerate").WithName(policyKey) logger.Info("update URs on policy event") if err := pc.syncDataPolicyChanges(policy, false); err != nil { logger.Error(err, "failed to create UR on policy event") return err } logger.V(4).Info("reconcile policy with generateExisting enabled") if err := pc.handleGenerateForExisting(policy); err != nil { logger.Error(err, "failed to create UR for generateExisting") return err } return nil } func (pc *policyController) syncDataPolicyChanges(policy kyvernov1.PolicyInterface, deleteDownstream bool) error { var errs []error var err error ur := newGenerateUR(policy) for _, rule := range policy.GetSpec().Rules { if !rule.HasGenerate() { continue } generate := rule.Generation if !generate.Synchronize { continue } if generate.GetData() != nil { if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, generate.GeneratePattern, deleteDownstream, false); err != nil { errs = append(errs, err) } } for _, foreach := range generate.ForEachGeneration { if foreach.GetData() != nil { if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, foreach.GeneratePattern, deleteDownstream, false); err != nil { errs = append(errs, err) } } } } if len(ur.Spec.RuleContext) == 0 { return multierr.Combine(errs...) } pc.log.V(2).WithName("syncDataPolicyChanges").Info("creating new UR for generate") created, err := pc.urGenerator.Generate(context.TODO(), pc.kyvernoClient, ur, pc.log) if err != nil { errs = append(errs, err) } if created != nil { updated := created.DeepCopy() updated.Status.State = kyvernov2.Pending _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) if err != nil { errs = append(errs, err) } } return multierr.Combine(errs...) } func (pc *policyController) handleGenerateForExisting(policy kyvernov1.PolicyInterface) error { var errors []error var triggers []*unstructured.Unstructured policyNew := policy.CreateDeepCopy() policyNew.GetSpec().Rules = nil ur := newGenerateUR(policy) logger := pc.log.WithName("handleGenerateForExisting") for _, rule := range policy.GetSpec().Rules { if !rule.HasGenerate() { continue } // check if the rule sets the generateExisting field. // if not, use the policy level setting generateExisting := rule.Generation.GenerateExisting if generateExisting != nil { if !*generateExisting { continue } } else if !policy.GetSpec().GenerateExisting { continue } triggers = getTriggers(pc.client, rule, policy.IsNamespaced(), policy.GetNamespace(), pc.log) policyNew.GetSpec().SetRules([]kyvernov1.Rule{rule}) for _, trigger := range triggers { namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), pc.nsLister, pc.log) policyContext, err := common.NewBackgroundContext(pc.log, pc.client, ur.Spec.Context, policy, trigger, pc.configuration, pc.jp, namespaceLabels) if err != nil { errors = append(errors, fmt.Errorf("failed to build policy context for rule %s: %w", rule.Name, err)) continue } engineResponse := pc.engine.ApplyBackgroundChecks(context.TODO(), policyContext) if len(engineResponse.PolicyResponse.Rules) == 0 { continue } logger.V(4).Info("adding rule context", "rule", rule.Name, "trigger", trigger.GetNamespace()+"/"+trigger.GetName()) addRuleContext(ur, rule.Name, common.ResourceSpecFromUnstructured(*trigger), false) } } if len(ur.Spec.RuleContext) == 0 { return multierr.Combine(errors...) } logger.V(2).Info("creating new UR for generate") created, err := pc.urGenerator.Generate(context.TODO(), pc.kyvernoClient, ur, pc.log) if err != nil { errors = append(errors, err) return multierr.Combine(errors...) } if created != nil { updated := created.DeepCopy() updated.Status.State = kyvernov2.Pending _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) if err != nil { errors = append(errors, err) return multierr.Combine(errors...) } pc.log.V(4).Info("successfully created UR on policy update", "policy", policyNew.GetName()) } return multierr.Combine(errors...) } func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.PolicyInterface) error { var errs []error var err error rules := autogen.Default.ComputeRules(policy, "") ur := newGenerateUR(policy) for _, r := range rules { if !r.HasGenerate() { continue } generate := r.Generation if !generate.Synchronize { continue } sync, orphanDownstreamOnPolicyDelete := r.GetSyncAndOrphanDownstream() if generate.GetData() != nil { if sync && (generate.GetType() == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete { if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r.Name, r.Generation.GeneratePattern, true, true); err != nil { errs = append(errs, err) } } } for _, foreach := range generate.ForEachGeneration { if foreach.GetData() != nil { if sync && (foreach.GetType() == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete { if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r.Name, foreach.GeneratePattern, true, true); err != nil { errs = append(errs, err) } } } } } if len(ur.Spec.RuleContext) == 0 { return multierr.Combine(errs...) } pc.log.V(2).WithName("createURForDownstreamDeletion").Info("creating new UR for generate") created, err := pc.urGenerator.Generate(context.TODO(), pc.kyvernoClient, ur, pc.log) if err != nil { errs = append(errs, err) } if created != nil { updated := created.DeepCopy() updated.Status.State = kyvernov2.Pending updated.Status.GeneratedResources = ur.Status.GeneratedResources _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) if err != nil { errs = append(errs, err) } } return multierr.Combine(errs...) } func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest, ruleName string, pattern kyvernov1.GeneratePattern, deleteDownstream, policyDeletion bool) (*kyvernov2.UpdateRequest, error) { labels := map[string]string{ common.GeneratePolicyLabel: policy.GetName(), common.GeneratePolicyNamespaceLabel: policy.GetNamespace(), common.GenerateRuleLabel: ruleName, kyverno.LabelAppManagedBy: kyverno.ValueKyvernoApp, } downstreams, err := common.FindDownstream(pc.client, pattern.GetAPIVersion(), pattern.GetKind(), labels) if err != nil { return ur, err } if len(downstreams.Items) == 0 { return ur, nil } pc.log.V(4).Info("sync data rule changes to downstream targets") for _, downstream := range downstreams.Items { labels := downstream.GetLabels() trigger := generateutils.TriggerFromLabels(labels) addRuleContext(ur, ruleName, trigger, deleteDownstream) if policyDeletion { addGeneratedResources(ur, downstream) } } return ur, nil } func (pc *policyController) unlabelDownstream(selector updatedResource) { for _, ruleSelector := range selector.ruleResources { for _, kind := range ruleSelector.kinds { updated, err := pc.client.ListResource(context.TODO(), "", kind, "", &metav1.LabelSelector{ MatchLabels: map[string]string{ backgroundcommon.GeneratePolicyLabel: selector.policy, backgroundcommon.GeneratePolicyNamespaceLabel: selector.policyNamespace, backgroundcommon.GenerateRuleLabel: ruleSelector.rule, }, }, ) if err != nil { utilruntime.HandleError(fmt.Errorf("failed to list old targets: %v", err)) continue } for _, obj := range updated.Items { labels := obj.GetLabels() delete(labels, backgroundcommon.GeneratePolicyLabel) delete(labels, backgroundcommon.GeneratePolicyNamespaceLabel) delete(labels, backgroundcommon.GenerateRuleLabel) obj.SetLabels(labels) _, err = pc.client.UpdateResource(context.TODO(), obj.GetAPIVersion(), obj.GetKind(), obj.GetNamespace(), &obj, false) if err != nil { utilruntime.HandleError(fmt.Errorf("failed to un-label old targets %s/%s/%s/%s: %v", obj.GetAPIVersion(), obj.GetKind(), obj.GetNamespace(), obj.GetName(), err)) continue } } } } } type updatedResource struct { policy string policyNamespace string ruleResources []ruleResource } type ruleResource struct { rule string kinds []string } // ruleDeletion returns true if any rule is deleted, along with deleted rules func ruleChange(old, new kyvernov1.PolicyInterface) (_ kyvernov1.PolicyInterface, ruleDeleted bool, _ updatedResource) { if !new.GetDeletionTimestamp().IsZero() { return nil, false, updatedResource{} } newRules := new.GetSpec().Rules oldRules := old.GetSpec().Rules newRulesMap := make(map[string]kyvernov1.Rule, len(newRules)) var deletedRules []kyvernov1.Rule updatedResources := updatedResource{ policy: new.GetName(), policyNamespace: new.GetNamespace(), } for _, r := range newRules { newRulesMap[r.Name] = r } for _, oldRule := range oldRules { if newRule, exist := newRulesMap[oldRule.Name]; !exist { deletedRules = append(deletedRules, oldRule) ruleDeleted = true } else { ruleRsrc := ruleResource{rule: oldRule.Name} old, new := oldRule.Generation, newRule.Generation if old.ResourceSpec != new.ResourceSpec || old.Clone != new.Clone { ruleRsrc.kinds = append(ruleRsrc.kinds, old.ResourceSpec.GetKind()) } if !datautils.DeepEqual(old.CloneList, new.CloneList) { ruleRsrc.kinds = append(ruleRsrc.kinds, old.CloneList.Kinds...) } for _, oldForeach := range old.ForEachGeneration { for _, newForeach := range new.ForEachGeneration { if oldForeach.List == newForeach.List { if oldForeach.ResourceSpec != newForeach.ResourceSpec || oldForeach.Clone != newForeach.Clone { ruleRsrc.kinds = append(ruleRsrc.kinds, old.ResourceSpec.GetKind()) } if !datautils.DeepEqual(oldForeach.CloneList, newForeach.CloneList) { ruleRsrc.kinds = append(ruleRsrc.kinds, old.CloneList.Kinds...) } } } } updatedResources.ruleResources = append(updatedResources.ruleResources, ruleRsrc) } } return buildPolicyWithDeletedRules(old, deletedRules), ruleDeleted, updatedResources } func buildPolicyWithDeletedRules(policy kyvernov1.PolicyInterface, deletedRules []kyvernov1.Rule) kyvernov1.PolicyInterface { newPolicy := policy.CreateDeepCopy() spec := newPolicy.GetSpec() spec.SetRules(deletedRules) return newPolicy }