mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
Allow non-object type elements for foreach rules (#3763)
Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
This commit is contained in:
parent
80abda568e
commit
0a5f004047
7 changed files with 113 additions and 17 deletions
|
@ -59,7 +59,7 @@ type Interface interface {
|
||||||
AddNamespace(namespace string) error
|
AddNamespace(namespace string) error
|
||||||
|
|
||||||
// AddElement adds element info to the context
|
// AddElement adds element info to the context
|
||||||
AddElement(data map[string]interface{}, index int) error
|
AddElement(data interface{}, index int) error
|
||||||
|
|
||||||
// AddImageInfo adds image info to the context
|
// AddImageInfo adds image info to the context
|
||||||
AddImageInfo(info kubeutils.ImageInfo) error
|
AddImageInfo(info kubeutils.ImageInfo) error
|
||||||
|
@ -223,7 +223,7 @@ func (ctx *context) AddNamespace(namespace string) error {
|
||||||
return addToContext(ctx, namespace, "request", "namespace")
|
return addToContext(ctx, namespace, "request", "namespace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) AddElement(data map[string]interface{}, index int) error {
|
func (ctx *context) AddElement(data interface{}, index int) error {
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"element": data,
|
"element": data,
|
||||||
"elementIndex": index,
|
"elementIndex": index,
|
||||||
|
|
|
@ -215,7 +215,8 @@ func mutateElements(name string, foreach kyverno.ForEachMutation, ctx *PolicyCon
|
||||||
ctx.JSONContext.Reset()
|
ctx.JSONContext.Reset()
|
||||||
ctx := ctx.Copy()
|
ctx := ctx.Copy()
|
||||||
store.SetForeachElement(i)
|
store.SetForeachElement(i)
|
||||||
if err := addElementToContext(ctx, e, i, false); err != nil {
|
falseVar := false
|
||||||
|
if err := addElementToContext(ctx, e, i, &falseVar); err != nil {
|
||||||
return mutateError(err, fmt.Sprintf("failed to add element to mutate.foreach[%d].context", i))
|
return mutateError(err, fmt.Sprintf("failed to add element to mutate.foreach[%d].context", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -274,12 +274,7 @@ func (v *validator) validateForEach() *response.RuleResponse {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
elementScope := true
|
resp, count := v.validateElements(foreach, elements, foreach.ElementScope)
|
||||||
if foreach.ElementScope != nil {
|
|
||||||
elementScope = *foreach.ElementScope
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, count := v.validateElements(foreach, elements, elementScope)
|
|
||||||
if resp.Status != response.RuleStatusPass {
|
if resp.Status != response.RuleStatusPass {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
@ -294,7 +289,7 @@ func (v *validator) validateForEach() *response.RuleResponse {
|
||||||
return ruleResponse(*v.rule, response.Validation, "rule passed", response.RuleStatusPass, nil)
|
return ruleResponse(*v.rule, response.Validation, "rule passed", response.RuleStatusPass, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validator) validateElements(foreach kyverno.ForEachValidation, elements []interface{}, elementScope bool) (*response.RuleResponse, int) {
|
func (v *validator) validateElements(foreach kyverno.ForEachValidation, elements []interface{}, elementScope *bool) (*response.RuleResponse, int) {
|
||||||
v.ctx.JSONContext.Checkpoint()
|
v.ctx.JSONContext.Checkpoint()
|
||||||
defer v.ctx.JSONContext.Restore()
|
defer v.ctx.JSONContext.Restore()
|
||||||
applyCount := 0
|
applyCount := 0
|
||||||
|
@ -328,17 +323,34 @@ func (v *validator) validateElements(foreach kyverno.ForEachValidation, elements
|
||||||
return ruleResponse(*v.rule, response.Validation, "", response.RuleStatusPass, nil), applyCount
|
return ruleResponse(*v.rule, response.Validation, "", response.RuleStatusPass, nil), applyCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, elementScope bool) error {
|
func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, elementScope *bool) error {
|
||||||
data, err := utils.ToMap(e)
|
data, err := variables.DocumentToUntyped(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ctx.JSONContext.AddElement(data, elementIndex); err != nil {
|
if err := ctx.JSONContext.AddElement(data, elementIndex); err != nil {
|
||||||
return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
|
return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
|
||||||
}
|
}
|
||||||
if elementScope {
|
dataMap, ok := data.(map[string]interface{})
|
||||||
|
// We set scoped to true by default if the data is a map
|
||||||
|
// otherwise we do not do element scoped foreach unless the user
|
||||||
|
// has explicitly set it to true
|
||||||
|
scoped := ok
|
||||||
|
|
||||||
|
// If the user has explicitly provided an element scope
|
||||||
|
// we check if data is a map or not. In case it is not a map and the user
|
||||||
|
// has set elementscoped to true, we throw an error.
|
||||||
|
// Otherwise we set the value to what is specified by the user.
|
||||||
|
if elementScope != nil {
|
||||||
|
if *elementScope && !ok {
|
||||||
|
return fmt.Errorf("cannot use elementScope=true foreach rules for elements that are not maps, expected type=map got type=%T", data)
|
||||||
|
}
|
||||||
|
scoped = *elementScope
|
||||||
|
}
|
||||||
|
|
||||||
|
if scoped {
|
||||||
u := unstructured.Unstructured{}
|
u := unstructured.Unstructured{}
|
||||||
u.SetUnstructuredContent(data)
|
u.SetUnstructuredContent(dataMap)
|
||||||
ctx.Element = u
|
ctx.Element = u
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -31,9 +31,9 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allowedVariables = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element\.|elementIndex|@|images\.|([a-z_0-9]+\()[^{}]`)
|
var allowedVariables = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images\.|([a-z_0-9]+\()[^{}]`)
|
||||||
|
|
||||||
var allowedVariablesBackground = regexp.MustCompile(`request\.|element\.|elementIndex|@|images\.|([a-z_0-9]+\()[^{}]`)
|
var allowedVariablesBackground = regexp.MustCompile(`request\.|element|elementIndex|@|images\.|([a-z_0-9]+\()[^{}]`)
|
||||||
|
|
||||||
// wildCardAllowedVariables represents regex for the allowed fields in wildcards
|
// wildCardAllowedVariables represents regex for the allowed fields in wildcards
|
||||||
var wildCardAllowedVariables = regexp.MustCompile(`\{\{\s*(request\.|serviceAccountName|serviceAccountNamespace)[^{}]*\}\}`)
|
var wildCardAllowedVariables = regexp.MustCompile(`\{\{\s*(request\.|serviceAccountName|serviceAccountNamespace)[^{}]*\}\}`)
|
||||||
|
|
|
@ -3,7 +3,6 @@ policies:
|
||||||
- policies.yaml
|
- policies.yaml
|
||||||
resources:
|
resources:
|
||||||
- resources.yaml
|
- resources.yaml
|
||||||
variables: variables.yaml
|
|
||||||
results:
|
results:
|
||||||
- policy: validate-empty-dir-mountpath
|
- policy: validate-empty-dir-mountpath
|
||||||
rule: check-mount-paths
|
rule: check-mount-paths
|
||||||
|
@ -40,3 +39,18 @@ results:
|
||||||
resource: test-pod-with-resources-multiple-ctnrs
|
resource: test-pod-with-resources-multiple-ctnrs
|
||||||
kind: Pod
|
kind: Pod
|
||||||
status: pass
|
status: pass
|
||||||
|
- policy: validate-image-list
|
||||||
|
rule: check-image
|
||||||
|
resource: test-pod
|
||||||
|
kind: Pod
|
||||||
|
status: fail
|
||||||
|
- policy: validate-image-list
|
||||||
|
rule: check-image
|
||||||
|
resource: test-pod-ghcr
|
||||||
|
kind: Pod
|
||||||
|
status: fail
|
||||||
|
- policy: validate-image-list-error
|
||||||
|
rule: check-image
|
||||||
|
resource: test-pod-ghcr
|
||||||
|
kind: Pod
|
||||||
|
status: error
|
||||||
|
|
|
@ -48,3 +48,48 @@ spec:
|
||||||
ephemeral-storage: "?*"
|
ephemeral-storage: "?*"
|
||||||
limits:
|
limits:
|
||||||
ephemeral-storage: "?*"
|
ephemeral-storage: "?*"
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: validate-image-list
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- name: check-image
|
||||||
|
match:
|
||||||
|
resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
validate:
|
||||||
|
message: "images must begin with ghcr.io"
|
||||||
|
foreach:
|
||||||
|
- list: "request.object.spec.containers[].image"
|
||||||
|
deny:
|
||||||
|
conditions:
|
||||||
|
all:
|
||||||
|
- key: '{{ element }}'
|
||||||
|
operator: NotEquals
|
||||||
|
value: 'ghcr.io'
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: validate-image-list-error
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- name: check-image
|
||||||
|
match:
|
||||||
|
resources:
|
||||||
|
kinds:
|
||||||
|
- Pod
|
||||||
|
validate:
|
||||||
|
message: "images must begin with ghcr.io"
|
||||||
|
foreach:
|
||||||
|
- list: "request.object.spec.containers[].image"
|
||||||
|
elementScope: true
|
||||||
|
deny:
|
||||||
|
conditions:
|
||||||
|
all:
|
||||||
|
- key: '{{ element }}'
|
||||||
|
operator: NotEquals
|
||||||
|
value: 'ghcr.io'
|
|
@ -24,6 +24,30 @@ spec:
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: test-pod-ghcr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: ghcr.io/test-webserver
|
||||||
|
name: test1
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp/cache
|
||||||
|
name: cache-volume
|
||||||
|
- image: ghcr.io/test-webserver
|
||||||
|
name: test2
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp/cache
|
||||||
|
name: cache-volume
|
||||||
|
- mountPath: /gce
|
||||||
|
name: gce
|
||||||
|
volumes:
|
||||||
|
- name: cache-volume
|
||||||
|
emptyDir: {}
|
||||||
|
- name: gce
|
||||||
|
gcePersistentDisk: {}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: test-pod2
|
name: test-pod2
|
||||||
spec:
|
spec:
|
||||||
|
|
Loading…
Reference in a new issue