mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-28 02:18:15 +00:00
Feat: cloneList rule validation (#7823)
* Feat: cloneList rule validation Signed-off-by: ShutingZhao <shuting@nirmata.com> * Test: add kuttl tests for npol Signed-off-by: ShutingZhao <shuting@nirmata.com> * Fix: split negative tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * Test: add kuttl tests for cpol Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
parent
2a1c82f174
commit
44c0206463
17 changed files with 415 additions and 14 deletions
|
@ -121,7 +121,7 @@ func (p *ClusterPolicy) ValidateSchema() bool {
|
|||
func (p *ClusterPolicy) Validate(clusterResources sets.Set[string]) (errs field.ErrorList) {
|
||||
errs = append(errs, ValidateAutogenAnnotation(field.NewPath("metadata").Child("annotations"), p.GetAnnotations())...)
|
||||
errs = append(errs, ValidatePolicyName(field.NewPath("name"), p.Name)...)
|
||||
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.Namespace, clusterResources)...)
|
||||
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.GetNamespace(), clusterResources)...)
|
||||
return errs
|
||||
}
|
||||
|
||||
|
|
|
@ -626,7 +626,7 @@ type CloneList struct {
|
|||
|
||||
func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
|
||||
if namespaced {
|
||||
if err := g.validateTargetsScope(clusterResources, policyNamespace); err != nil {
|
||||
if err := g.validateNamespacedTargetsScope(clusterResources, policyNamespace); err != nil {
|
||||
errs = append(errs, field.Forbidden(path.Child("generate").Child("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
|
||||
}
|
||||
}
|
||||
|
@ -669,6 +669,45 @@ func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace
|
|||
errs = append(errs, field.Forbidden(path.Child("generate").Child("name"), "name can not be empty"))
|
||||
}
|
||||
}
|
||||
|
||||
errs = append(errs, g.ValidateCloneList(path.Child("generate"), namespaced, policyNamespace, clusterResources)...)
|
||||
return errs
|
||||
}
|
||||
|
||||
func (g *Generation) ValidateCloneList(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
|
||||
if len(g.CloneList.Kinds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if namespaced {
|
||||
for _, kind := range g.CloneList.Kinds {
|
||||
if clusterResources.Has(kind) {
|
||||
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("kinds"), fmt.Sprintf("the source in cloneList must be a namespaced resource: %v", kind)))
|
||||
}
|
||||
if g.CloneList.Namespace != policyNamespace {
|
||||
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("namespace"), fmt.Sprintf("a namespaced policy cannot clone resources from other namespace, expected: %v, received: %v", policyNamespace, g.CloneList.Namespace)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clusterScope := clusterResources.Has(g.CloneList.Kinds[0])
|
||||
for _, gvk := range g.CloneList.Kinds[1:] {
|
||||
if clusterScope != clusterResources.Has(gvk) {
|
||||
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("kinds"), "mixed scope of target resources is forbidden"))
|
||||
break
|
||||
}
|
||||
clusterScope = clusterScope && clusterResources.Has(gvk)
|
||||
}
|
||||
|
||||
if !clusterScope {
|
||||
if g.CloneList.Namespace == "" {
|
||||
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("namespace"), "namespace is required for namespaced target resources"))
|
||||
}
|
||||
} else if clusterScope && !namespaced {
|
||||
if g.CloneList.Namespace != "" {
|
||||
errs = append(errs, field.Forbidden(path.Child("cloneList").Child("namespace"), "namespace is forbidden for cluster-wide target resources"))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
|
@ -680,7 +719,7 @@ func (g *Generation) SetData(in apiextensions.JSON) {
|
|||
g.RawData = ToJSON(in)
|
||||
}
|
||||
|
||||
func (g *Generation) validateTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
|
||||
func (g *Generation) validateNamespacedTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
|
||||
target := g.ResourceSpec
|
||||
if clusterResources.Has(target.GetAPIVersion() + "/" + target.GetKind()) {
|
||||
return fmt.Errorf("the target must be a namespaced resource: %v/%v", target.GetAPIVersion(), target.GetKind())
|
||||
|
@ -695,16 +734,6 @@ func (g *Generation) validateTargetsScope(clusterResources sets.Set[string], pol
|
|||
return fmt.Errorf("a namespaced policy cannot clone resources from other namespaces, expected: %v, received: %v", policyNamespace, g.Clone.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
for _, kind := range g.CloneList.Kinds {
|
||||
if clusterResources.Has(kind) {
|
||||
return fmt.Errorf("the source in cloneList must be a namespaced resource: %v/%v", target.GetAPIVersion(), target.GetKind())
|
||||
}
|
||||
if g.CloneList.Namespace != policyNamespace {
|
||||
return fmt.Errorf("a namespaced policy cannot clone resources from other namespace, expected: %v, received: %v", policyNamespace, g.CloneList.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ func (p *Policy) ValidateSchema() bool {
|
|||
func (p *Policy) Validate(clusterResources sets.Set[string]) (errs field.ErrorList) {
|
||||
errs = append(errs, ValidateAutogenAnnotation(field.NewPath("metadata").Child("annotations"), p.GetAnnotations())...)
|
||||
errs = append(errs, ValidatePolicyName(field.NewPath("name"), p.Name)...)
|
||||
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.Namespace, clusterResources)...)
|
||||
errs = append(errs, p.Spec.Validate(field.NewPath("spec"), p.IsNamespaced(), p.GetNamespace(), clusterResources)...)
|
||||
return errs
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: kyverno:background-controller:manage-clusterrole
|
||||
labels:
|
||||
app.kubernetes.io/component: background-controller
|
||||
app.kubernetes.io/instance: kyverno
|
||||
app.kubernetes.io/part-of: kyverno
|
||||
rules:
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- clusterroles
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- get
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: policy-pass.yaml
|
||||
shouldFail: false
|
||||
- file: policy-fail-1.yaml
|
||||
shouldFail: true
|
||||
- file: policy-fail-2.yaml
|
||||
shouldFail: true
|
||||
- file: policy-fail-3.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,15 @@
|
|||
## Description
|
||||
|
||||
This test validate cloneList sources scopes and the namespace settings.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
These tests checks:
|
||||
1. the mixed scoped of clone sources cannot be defined
|
||||
2. the namespace must be set if clone namespaced resources
|
||||
3. the namespace must not be set if clone cluster-wide resources
|
||||
|
||||
|
||||
## Reference Issue(s)
|
||||
|
||||
https://github.com/kyverno/kyverno/issues/7801
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cpol-target-scope-validation-fail-1
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ServiceAccount
|
||||
generate:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize: true
|
||||
cloneList:
|
||||
# mixed scope
|
||||
kinds:
|
||||
- v1/Secret
|
||||
- rbac.authorization.k8s.io/v1/ClusterRole
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cpol-target-scope-validation-fail-2
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
generate:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize: true
|
||||
cloneList:
|
||||
# ns is forbidden for cluster-wide resource
|
||||
namespace: default
|
||||
kinds:
|
||||
- rbac.authorization.k8s.io/v1/ClusterRole
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: cpol-target-scope-validation-fail-3
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
generate:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize: true
|
||||
cloneList:
|
||||
# missing ns for namespaced resource
|
||||
kinds:
|
||||
- v1/Secret
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: target-scope-validation-pass-1
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
generate:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize: true
|
||||
cloneList:
|
||||
namespace: default
|
||||
kinds:
|
||||
- v1/Secret
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
||||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: target-scope-validation-pass-2
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
generate:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize: true
|
||||
cloneList:
|
||||
kinds:
|
||||
- rbac.authorization.k8s.io/v1/ClusterRole
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
||||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: target-scope-validation-pass-3
|
||||
spec:
|
||||
generateExisting: false
|
||||
rules:
|
||||
- name: sync-secret
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- default
|
||||
- kube-public
|
||||
- kyverno
|
||||
generate:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize : true
|
||||
cloneList:
|
||||
namespace: default
|
||||
kinds:
|
||||
- v1/Secret
|
||||
- v1/ConfigMap
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: target-scope-validation-fail-ns
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- file: policy-pass.yaml
|
||||
shouldFail: false
|
||||
- file: policy-fail-1.yaml
|
||||
shouldFail: true
|
||||
- file: policy-fail-2.yaml
|
||||
shouldFail: true
|
||||
- file: policy-fail-3.yaml
|
||||
shouldFail: true
|
|
@ -0,0 +1,15 @@
|
|||
## Description
|
||||
|
||||
This test validate clone sources scopes and the namespace settings.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
These tests checks:
|
||||
1. the mixed scoped of clone sources cannot be defined
|
||||
2. a namespace policy cannot clone a cluster-wide resource
|
||||
3. the clone source namespace must be set for a namespaced policy
|
||||
|
||||
|
||||
## Reference Issue(s)
|
||||
|
||||
https://github.com/kyverno/kyverno/issues/7801
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: target-scope-validation-fail-1
|
||||
namespace: target-scope-validation-fail-ns
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ServiceAccount
|
||||
generate:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
synchronize: true
|
||||
cloneList:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
# mixed scope of resources
|
||||
kinds:
|
||||
- v1/Secret
|
||||
- rbac.authorization.k8s.io/v1/ClusterRole
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: target-scope-validation-fail-2
|
||||
namespace: target-scope-validation-fail-ns
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ServiceAccount
|
||||
generate:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
synchronize: true
|
||||
cloneList:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
kinds:
|
||||
# namespace policy cannot generate cluster scoped resources
|
||||
- rbac.authorization.k8s.io/v1/ClusterRole
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: target-scope-validation-fail-3
|
||||
namespace: target-scope-validation-fail-ns
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ServiceAccount
|
||||
generate:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
synchronize: true
|
||||
cloneList:
|
||||
# missing namespace for npol
|
||||
# namespace: target-scope-validation-fail-ns
|
||||
kinds:
|
||||
- v1/Secret
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,73 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: target-scope-validation-pass-1
|
||||
namespace: target-scope-validation-fail-ns
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ServiceAccount
|
||||
generate:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
synchronize: true
|
||||
cloneList:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
kinds:
|
||||
- v1/Secret
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
||||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: target-scope-validation-pass-2
|
||||
namespace: target-scope-validation-fail-ns
|
||||
spec:
|
||||
rules:
|
||||
- name: clone-multiple-basic-create-policy-rule
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ServiceAccount
|
||||
generate:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
synchronize: true
|
||||
cloneList:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
kinds:
|
||||
- v1/Secret
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
||||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: target-scope-validation-pass-3
|
||||
namespace: target-scope-validation-fail-ns
|
||||
spec:
|
||||
generateExisting: false
|
||||
rules:
|
||||
- name: sync-secret
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
generate:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
synchronize : true
|
||||
cloneList:
|
||||
namespace: target-scope-validation-fail-ns
|
||||
kinds:
|
||||
- v1/Secret
|
||||
- v1/ConfigMap
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
Loading…
Add table
Reference in a new issue