mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
fix: generate policy validation to prevent endless loop (#7026)
* refactor policy validation Signed-off-by: ShutingZhao <shuting@nirmata.com> * add loop check for generate Signed-off-by: ShutingZhao <shuting@nirmata.com> * add kuttl tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * linter fixes Signed-off-by: ShutingZhao <shuting@nirmata.com> * linter fixes Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
parent
0edbb3038a
commit
f87b0204e6
27 changed files with 304 additions and 223 deletions
|
@ -21,8 +21,8 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/config"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||
gitutils "github.com/kyverno/kyverno/pkg/utils/git"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
@ -388,7 +388,7 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
|
|||
skipInvalidPolicies.invalid = make([]string, 0)
|
||||
|
||||
for _, policy := range policies {
|
||||
_, err := policy2.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
|
||||
_, err := policyvalidation.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
|
||||
if err != nil {
|
||||
log.Log.Error(err, "policy validation error")
|
||||
if strings.HasPrefix(err.Error(), "variable 'element.name'") {
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/policy"
|
||||
policyutils "github.com/kyverno/kyverno/pkg/utils/policy"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/multierr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/config"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
"golang.org/x/exp/slices"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
@ -179,7 +179,7 @@ func applyPoliciesFromPath(
|
|||
}
|
||||
|
||||
for _, policy := range policies {
|
||||
_, err := policy2.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
|
||||
_, err := policyvalidation.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
|
||||
if err != nil {
|
||||
log.Log.Error(err, "skipping invalid policy", "name", policy.GetName())
|
||||
continue
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
kyvernov1alpha2 "github.com/kyverno/kyverno/api/kyverno/v1alpha2"
|
||||
"github.com/kyverno/kyverno/pkg/autogen"
|
||||
"github.com/kyverno/kyverno/pkg/policy"
|
||||
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ func CanBackgroundProcess(p kyvernov1.PolicyInterface) bool {
|
|||
if !p.BackgroundProcessingEnabled() {
|
||||
return false
|
||||
}
|
||||
if err := policy.ValidateVariables(p, true); err != nil {
|
||||
if err := policyvalidation.ValidateVariables(p, true); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
||||
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -147,7 +148,7 @@ func (pc *policyController) canBackgroundProcess(p kyvernov1.PolicyInterface) bo
|
|||
}
|
||||
}
|
||||
|
||||
if err := ValidateVariables(p, true); err != nil {
|
||||
if err := policyvalidation.ValidateVariables(p, true); err != nil {
|
||||
logger.V(4).Info("policy cannot be processed in the background")
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ var forbidden = []*regexp.Regexp{
|
|||
regexp.MustCompile(`[^\.](request.clusterRoles)\b`),
|
||||
}
|
||||
|
||||
// ContainsUserVariables returns error if variable that does not start from request.object
|
||||
// containsUserVariables returns error if variable that does not start from request.object
|
||||
func containsUserVariables(policy kyvernov1.PolicyInterface, vars [][]string) error {
|
||||
rules := autogen.ComputeRules(policy)
|
||||
for idx := range rules {
|
140
pkg/validation/policy/generate.go
Normal file
140
pkg/validation/policy/generate.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"crypto/md5" //nolint:gosec
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/utils/strings/slices"
|
||||
)
|
||||
|
||||
func immutableGenerateFields(new, old kyvernov1.PolicyInterface) error {
|
||||
if new == nil || old == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !new.GetSpec().HasGenerate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldRuleHashes, err := buildHashes(old.GetSpec().Rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRuleHashes, err := buildHashes(new.GetSpec().Rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch len(old.GetSpec().Rules) <= len(new.GetSpec().Rules) {
|
||||
case true:
|
||||
if newRuleHashes.IsSuperset(oldRuleHashes) {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("change of immutable fields for a generate rule is disallowed")
|
||||
}
|
||||
case false:
|
||||
if oldRuleHashes.IsSuperset(newRuleHashes) {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("rule deletion - change of immutable fields for a generate rule is disallowed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkClusterResourceInMatchAndExclude returns false if namespaced ClusterPolicy contains cluster wide resources in
|
||||
// Match and Exclude block
|
||||
func checkClusterResourceInMatchAndExclude(rule kyvernov1.Rule, clusterResources sets.Set[string], policyNamespace string, mock bool, res []*metav1.APIResourceList) error {
|
||||
if !mock {
|
||||
// Check for generate policy
|
||||
// - if resource to be generated is namespaced resource then the namespace field
|
||||
// should be mentioned
|
||||
// - if resource to be generated is non namespaced resource then the namespace field
|
||||
// should not be mentioned
|
||||
if rule.HasGenerate() {
|
||||
generateResourceKind := rule.Generation.Kind
|
||||
for _, resList := range res {
|
||||
for _, r := range resList.APIResources {
|
||||
if r.Kind == generateResourceKind {
|
||||
if r.Namespaced {
|
||||
if rule.Generation.Namespace == "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: please mention the namespace to generate a namespaced resource", rule.Name)
|
||||
}
|
||||
if rule.Generation.Namespace != policyNamespace {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate resources in other namespaces, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Namespace)
|
||||
}
|
||||
if rule.Generation.Clone.Name != "" {
|
||||
if rule.Generation.Clone.Namespace != policyNamespace {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot clone resources to or from other namespaces, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Clone.Namespace)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if rule.Generation.Namespace != "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: do not mention the namespace to generate a non namespaced resource", rule.Name)
|
||||
}
|
||||
if policyNamespace != "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources", rule.Name)
|
||||
}
|
||||
}
|
||||
} else if len(rule.Generation.CloneList.Kinds) != 0 {
|
||||
for _, kind := range rule.Generation.CloneList.Kinds {
|
||||
_, splitkind := kubeutils.GetKindFromGVK(kind)
|
||||
if r.Kind == splitkind {
|
||||
if r.Namespaced {
|
||||
if rule.Generation.CloneList.Namespace != policyNamespace {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot clone resource in other namespace, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Namespace)
|
||||
}
|
||||
} else {
|
||||
if policyNamespace != "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources", rule.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loopInGenerate(rule kyvernov1.Rule) error {
|
||||
if !rule.HasGenerate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if slices.Contains(rule.MatchResources.GetKinds(), rule.Generation.Kind) {
|
||||
return fmt.Errorf("the rule would result in an endless loop, the trigger and the target resources are the same kind: %s", rule.Generation.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetMutableFields(rule kyvernov1.Rule) *kyvernov1.Rule {
|
||||
new := new(kyvernov1.Rule)
|
||||
rule.DeepCopyInto(new)
|
||||
new.Generation.Synchronize = true
|
||||
new.Generation.SetData(nil)
|
||||
return new
|
||||
}
|
||||
|
||||
func buildHashes(rules []kyvernov1.Rule) (sets.Set[string], error) {
|
||||
ruleHashes := sets.New[string]()
|
||||
for _, rule := range rules {
|
||||
r := resetMutableFields(rule)
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return ruleHashes, fmt.Errorf("failed to create hash from the generate rule %v", err)
|
||||
}
|
||||
hash := md5.Sum(data) //nolint:gosec
|
||||
ruleHashes.Insert(hex.EncodeToString(hash[:]))
|
||||
}
|
||||
return ruleHashes, nil
|
||||
}
|
|
@ -2,8 +2,6 @@ package policy
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5" //nolint:gosec
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -185,79 +183,47 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
|
|||
}
|
||||
}
|
||||
|
||||
if err := immutableGenerateFields(policy, oldPolicy); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
rules := autogen.ComputeRules(policy)
|
||||
rulesPath := specPath.Child("rules")
|
||||
|
||||
for _, rule := range rules {
|
||||
// Validate Kind with match resource kinds
|
||||
for i, rule := range rules {
|
||||
match := rule.MatchResources
|
||||
exclude := rule.ExcludeResources
|
||||
for _, value := range match.Any {
|
||||
wildcardErr := validateWildcard(value.ResourceDescription.Kinds, spec, rule)
|
||||
if wildcardErr != nil {
|
||||
return warnings, wildcardErr
|
||||
}
|
||||
if !slices.Contains(value.ResourceDescription.Kinds, "*") {
|
||||
err := validateKinds(value.ResourceDescription.Kinds, mock, background, rule.HasValidate(), client)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("the kind defined in the any match resource is invalid: %w", err)
|
||||
}
|
||||
for j, value := range match.Any {
|
||||
if err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d].match.any[%d].kinds: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
for _, value := range match.All {
|
||||
wildcardErr := validateWildcard(value.ResourceDescription.Kinds, spec, rule)
|
||||
if wildcardErr != nil {
|
||||
return warnings, wildcardErr
|
||||
}
|
||||
if !slices.Contains(value.ResourceDescription.Kinds, "*") {
|
||||
err := validateKinds(value.ResourceDescription.Kinds, mock, background, rule.HasValidate(), client)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("the kind defined in the all match resource is invalid: %w", err)
|
||||
}
|
||||
for j, value := range match.All {
|
||||
if err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d].match.all[%d].kinds: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
for _, value := range exclude.Any {
|
||||
wildcardErr := validateWildcard(value.ResourceDescription.Kinds, spec, rule)
|
||||
if wildcardErr != nil {
|
||||
return warnings, wildcardErr
|
||||
}
|
||||
if !slices.Contains(value.ResourceDescription.Kinds, "*") {
|
||||
err := validateKinds(value.ResourceDescription.Kinds, mock, background, rule.HasValidate(), client)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("the kind defined in the any exclude resource is invalid: %w", err)
|
||||
}
|
||||
for j, value := range exclude.Any {
|
||||
if err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d].exclude.any[%d].kinds: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
for _, value := range exclude.All {
|
||||
wildcardErr := validateWildcard(value.ResourceDescription.Kinds, spec, rule)
|
||||
if wildcardErr != nil {
|
||||
return warnings, wildcardErr
|
||||
}
|
||||
if !slices.Contains(value.ResourceDescription.Kinds, "*") {
|
||||
err := validateKinds(value.ResourceDescription.Kinds, mock, background, rule.HasValidate(), client)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("the kind defined in the all exclude resource is invalid: %w", err)
|
||||
}
|
||||
for j, value := range exclude.All {
|
||||
if err := validateKinds(value.ResourceDescription.Kinds, rule, mock, background, client); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d].exclude.all[%d].kinds: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
if !slices.Contains(rule.MatchResources.Kinds, "*") {
|
||||
err := validateKinds(rule.MatchResources.Kinds, mock, background, rule.HasValidate(), client)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("match resource kind is invalid: %w", err)
|
||||
}
|
||||
err = validateKinds(rule.ExcludeResources.Kinds, mock, background, rule.HasValidate(), client)
|
||||
if err != nil {
|
||||
return warnings, fmt.Errorf("exclude resource kind is invalid: %w", err)
|
||||
}
|
||||
} else {
|
||||
wildcardErr := validateWildcard(rule.MatchResources.Kinds, spec, rule)
|
||||
if wildcardErr != nil {
|
||||
return warnings, wildcardErr
|
||||
}
|
||||
wildcardErr = validateWildcard(rule.ExcludeResources.Kinds, spec, rule)
|
||||
if wildcardErr != nil {
|
||||
return warnings, wildcardErr
|
||||
}
|
||||
|
||||
if err := validateKinds(rule.MatchResources.Kinds, rule, mock, background, client); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d].match.kinds: %v", i, err)
|
||||
}
|
||||
|
||||
if err := validateKinds(rule.ExcludeResources.Kinds, rule, mock, background, client); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d].exclude.kinds: %v", i, err)
|
||||
}
|
||||
|
||||
if err := loopInGenerate(rule); err != nil {
|
||||
return warnings, fmt.Errorf("path: spec.rules[%d]: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,10 +282,6 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
|
|||
}
|
||||
}
|
||||
|
||||
if err := immutableGenerateFields(policy, oldPolicy); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
// validate Cluster Resources in namespaced policy
|
||||
// For namespaced policy, ClusterResource type field and values are not allowed in match and exclude
|
||||
if namespaced {
|
||||
|
@ -1133,63 +1095,6 @@ func validateMatchedResourceDescription(rd kyvernov1.ResourceDescription) (strin
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// checkClusterResourceInMatchAndExclude returns false if namespaced ClusterPolicy contains cluster wide resources in
|
||||
// Match and Exclude block
|
||||
func checkClusterResourceInMatchAndExclude(rule kyvernov1.Rule, clusterResources sets.Set[string], policyNamespace string, mock bool, res []*metav1.APIResourceList) error {
|
||||
if !mock {
|
||||
// Check for generate policy
|
||||
// - if resource to be generated is namespaced resource then the namespace field
|
||||
// should be mentioned
|
||||
// - if resource to be generated is non namespaced resource then the namespace field
|
||||
// should not be mentioned
|
||||
if rule.HasGenerate() {
|
||||
generateResourceKind := rule.Generation.Kind
|
||||
for _, resList := range res {
|
||||
for _, r := range resList.APIResources {
|
||||
if r.Kind == generateResourceKind {
|
||||
if r.Namespaced {
|
||||
if rule.Generation.Namespace == "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: please mention the namespace to generate a namespaced resource", rule.Name)
|
||||
}
|
||||
if rule.Generation.Namespace != policyNamespace {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate resources in other namespaces, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Namespace)
|
||||
}
|
||||
if rule.Generation.Clone.Name != "" {
|
||||
if rule.Generation.Clone.Namespace != policyNamespace {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot clone resources to or from other namespaces, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Clone.Namespace)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if rule.Generation.Namespace != "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: do not mention the namespace to generate a non namespaced resource", rule.Name)
|
||||
}
|
||||
if policyNamespace != "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources", rule.Name)
|
||||
}
|
||||
}
|
||||
} else if len(rule.Generation.CloneList.Kinds) != 0 {
|
||||
for _, kind := range rule.Generation.CloneList.Kinds {
|
||||
_, splitkind := kubeutils.GetKindFromGVK(kind)
|
||||
if r.Kind == splitkind {
|
||||
if r.Namespaced {
|
||||
if rule.Generation.CloneList.Namespace != policyNamespace {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot clone resource in other namespace, expected: %v, received: %v", rule.Name, policyNamespace, rule.Generation.Namespace)
|
||||
}
|
||||
} else {
|
||||
if policyNamespace != "" {
|
||||
return fmt.Errorf("path: spec.rules[%v]: a namespaced policy cannot generate cluster-wide resources", rule.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// jsonPatchOnPod checks if a rule applies JSON patches to Pod
|
||||
func jsonPatchOnPod(rule kyvernov1.Rule) bool {
|
||||
if !rule.HasMutate() {
|
||||
|
@ -1218,13 +1123,28 @@ func podControllerAutoGenExclusion(policy kyvernov1.PolicyInterface) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func validateKinds(kinds []string, rule kyvernov1.Rule, mock, background bool, client dclient.Interface) error {
|
||||
if err := validateWildcard(kinds, background, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if slices.Contains(kinds, "*") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := validKinds(kinds, mock, background, rule.HasValidate(), client); err != nil {
|
||||
return fmt.Errorf("the kind defined in the all match resource is invalid: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateWildcard check for an Match/Exclude block contains "*"
|
||||
func validateWildcard(kinds []string, spec *kyvernov1.Spec, rule kyvernov1.Rule) error {
|
||||
if slices.Contains(kinds, "*") && spec.BackgroundProcessingEnabled() {
|
||||
func validateWildcard(kinds []string, background bool, rule kyvernov1.Rule) error {
|
||||
if slices.Contains(kinds, "*") && background {
|
||||
return fmt.Errorf("wildcard policy not allowed in background mode. Set spec.background=false to disable background mode for this policy rule ")
|
||||
}
|
||||
if slices.Contains(kinds, "*") && len(kinds) > 1 {
|
||||
return fmt.Errorf("wildard policy can not deal more than one kind")
|
||||
return fmt.Errorf("wildcard policy can not deal with more than one kind")
|
||||
}
|
||||
if slices.Contains(kinds, "*") {
|
||||
if rule.HasGenerate() || rule.HasVerifyImages() || rule.Validation.ForEachValidation != nil {
|
||||
|
@ -1264,10 +1184,10 @@ func validateWildcard(kinds []string, spec *kyvernov1.Spec, rule kyvernov1.Rule)
|
|||
return nil
|
||||
}
|
||||
|
||||
// validateKinds verifies if an API resource that matches 'kind' is valid kind
|
||||
// validKinds verifies if an API resource that matches 'kind' is valid kind
|
||||
// and found in the cache, returns error if not found. It also returns an error if background scanning
|
||||
// is enabled for a subresource.
|
||||
func validateKinds(kinds []string, mock, backgroundScanningEnabled, isValidationPolicy bool, client dclient.Interface) error {
|
||||
func validKinds(kinds []string, mock, backgroundScanningEnabled, isValidationPolicy bool, client dclient.Interface) error {
|
||||
if !mock {
|
||||
for _, k := range kinds {
|
||||
group, version, kind, subresource := kubeutils.ParseKindSelector(k)
|
||||
|
@ -1373,60 +1293,3 @@ func checkForStatusSubresource(ruleTypeJson []byte, allKinds []string, warnings
|
|||
*warnings = append(*warnings, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func immutableGenerateFields(new, old kyvernov1.PolicyInterface) error {
|
||||
if new == nil || old == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !new.GetSpec().HasGenerate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldRuleHashes, err := buildHashes(old.GetSpec().Rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRuleHashes, err := buildHashes(new.GetSpec().Rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch len(old.GetSpec().Rules) <= len(new.GetSpec().Rules) {
|
||||
case true:
|
||||
if newRuleHashes.IsSuperset(oldRuleHashes) {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("change of immutable fields for a generate rule is disallowed")
|
||||
}
|
||||
case false:
|
||||
if oldRuleHashes.IsSuperset(newRuleHashes) {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("rule deletion - change of immutable fields for a generate rule is disallowed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetMutableFields(rule kyvernov1.Rule) *kyvernov1.Rule {
|
||||
new := new(kyvernov1.Rule)
|
||||
rule.DeepCopyInto(new)
|
||||
new.Generation.Synchronize = true
|
||||
new.Generation.SetData(nil)
|
||||
return new
|
||||
}
|
||||
|
||||
func buildHashes(rules []kyvernov1.Rule) (sets.Set[string], error) {
|
||||
ruleHashes := sets.New[string]()
|
||||
for _, rule := range rules {
|
||||
r := resetMutableFields(rule)
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return ruleHashes, fmt.Errorf("failed to create hash from the generate rule %v", err)
|
||||
}
|
||||
hash := md5.Sum(data) //nolint:gosec
|
||||
ruleHashes.Insert(hex.EncodeToString(hash[:]))
|
||||
}
|
||||
return ruleHashes, nil
|
||||
}
|
|
@ -7,8 +7,8 @@ import (
|
|||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
policyvalidate "github.com/kyverno/kyverno/pkg/policy"
|
||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||
policyvalidate "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ spec:
|
|||
data:
|
||||
ZK_ADDRESS: "192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181"
|
||||
KAFKA_ADDRESS: "192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092"
|
||||
- name: super-secret
|
||||
- name: super-configmap
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
|
@ -44,14 +44,13 @@ spec:
|
|||
generate:
|
||||
synchronize: true
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
name: supersecret
|
||||
kind: ConfigMap
|
||||
name: superconfigmap
|
||||
namespace: pol-data-sync-delete-rule
|
||||
data:
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
somekey: somesecretvalue
|
||||
somekey: somevalue
|
||||
data:
|
||||
mysupersecretkey: bXlzdXBlcnNlY3JldHZhbHVl
|
||||
key: superconfigmap
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
assert:
|
||||
- secret.yaml
|
||||
- configmap.yaml
|
||||
- configmap.yaml
|
||||
- configmap-remain.yaml
|
|
@ -6,7 +6,7 @@ metadata:
|
|||
spec:
|
||||
generateExisting: false
|
||||
rules:
|
||||
- name: super-secret
|
||||
- name: super-configmap
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
|
@ -17,14 +17,13 @@ spec:
|
|||
generate:
|
||||
synchronize: true
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
name: supersecret
|
||||
kind: ConfigMap
|
||||
name: superconfigmap
|
||||
namespace: pol-data-sync-delete-rule
|
||||
data:
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
somekey: somesecretvalue
|
||||
somekey: somevalue
|
||||
data:
|
||||
mysupersecretkey: bXlzdXBlcnNlY3JldHZhbHVl
|
||||
key: superconfigmap
|
||||
|
|
|
@ -2,6 +2,6 @@ apiVersion: kuttl.dev/v1beta1
|
|||
kind: TestStep
|
||||
apply:
|
||||
assert:
|
||||
- secret.yaml
|
||||
- configmap-remain.yaml
|
||||
error:
|
||||
- configmap.yaml
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
key: superconfigmap
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
somekey: somevalue
|
||||
name: superconfigmap
|
||||
namespace: pol-data-sync-delete-rule
|
|
@ -1,10 +0,0 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
mysupersecretkey: bXlzdXBlcnNlY3JldHZhbHVl
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
somekey: somesecretvalue
|
||||
name: supersecret
|
||||
namespace: pol-data-sync-delete-rule
|
||||
type: Opaque
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: policy.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,12 @@
|
|||
## Description
|
||||
|
||||
This test ensures that a generate policy cannot have the same kind defined in the trigger and the target resources. Otherwise it would result in an endless loop.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
The test fails if the policy creation is allowed, otherwise passes.
|
||||
|
||||
|
||||
## Reference Issue(s)
|
||||
|
||||
https://github.com/kyverno/kyverno/issues/7017
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cpol-generate-prevent-loop
|
||||
spec:
|
||||
rules:
|
||||
- name: cpol-generate-prevent-loop
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
operations:
|
||||
- CREATE
|
||||
generate:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
name: corp-{{ random('[0-9a-z]{8}') }}
|
||||
namespace: "{{request.namespace}}"
|
||||
synchronize: false
|
||||
data:
|
||||
data:
|
||||
foo: bar
|
|
@ -11,7 +11,7 @@ spec:
|
|||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
- Secret
|
||||
generate:
|
||||
synchronize: true
|
||||
apiVersion: v1
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: policy.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,12 @@
|
|||
## Description
|
||||
|
||||
This test ensures that a generate policy cannot have the same kind defined in the trigger and the target resources. Otherwise it would result in an endless loop.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
The test fails if the policy creation is allowed, otherwise passes.
|
||||
|
||||
|
||||
## Reference Issue(s)
|
||||
|
||||
https://github.com/kyverno/kyverno/issues/7017
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: pol-generate-prevent-loop
|
||||
spec:
|
||||
rules:
|
||||
- name: pol-generate-prevent-loop
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
operations:
|
||||
- CREATE
|
||||
generate:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
name: corp-{{ random('[0-9a-z]{8}') }}
|
||||
namespace: "{{request.namespace}}"
|
||||
synchronize: false
|
||||
data:
|
||||
data:
|
||||
foo: bar
|
Loading…
Reference in a new issue