mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
a5e082303d
* refactor: introduce autogen interface Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix linter Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> --------- Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
331 lines
11 KiB
Go
331 lines
11 KiB
Go
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
|
|
}
|