1
0
Fork 0
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:
shuting 2023-07-17 17:13:22 +08:00 committed by GitHub
parent 2a1c82f174
commit 44c0206463
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 415 additions and 14 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: target-scope-validation-fail-ns

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"