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
|
||||
|
||||
// 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(info kubeutils.ImageInfo) error
|
||||
|
@ -223,7 +223,7 @@ func (ctx *context) AddNamespace(namespace string) error {
|
|||
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{}{
|
||||
"element": data,
|
||||
"elementIndex": index,
|
||||
|
|
|
@ -215,7 +215,8 @@ func mutateElements(name string, foreach kyverno.ForEachMutation, ctx *PolicyCon
|
|||
ctx.JSONContext.Reset()
|
||||
ctx := ctx.Copy()
|
||||
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))
|
||||
}
|
||||
|
||||
|
|
|
@ -274,12 +274,7 @@ func (v *validator) validateForEach() *response.RuleResponse {
|
|||
continue
|
||||
}
|
||||
|
||||
elementScope := true
|
||||
if foreach.ElementScope != nil {
|
||||
elementScope = *foreach.ElementScope
|
||||
}
|
||||
|
||||
resp, count := v.validateElements(foreach, elements, elementScope)
|
||||
resp, count := v.validateElements(foreach, elements, foreach.ElementScope)
|
||||
if resp.Status != response.RuleStatusPass {
|
||||
return resp
|
||||
}
|
||||
|
@ -294,7 +289,7 @@ func (v *validator) validateForEach() *response.RuleResponse {
|
|||
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()
|
||||
defer v.ctx.JSONContext.Restore()
|
||||
applyCount := 0
|
||||
|
@ -328,17 +323,34 @@ func (v *validator) validateElements(foreach kyverno.ForEachValidation, elements
|
|||
return ruleResponse(*v.rule, response.Validation, "", response.RuleStatusPass, nil), applyCount
|
||||
}
|
||||
|
||||
func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, elementScope bool) error {
|
||||
data, err := utils.ToMap(e)
|
||||
func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, elementScope *bool) error {
|
||||
data, err := variables.DocumentToUntyped(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.JSONContext.AddElement(data, elementIndex); err != nil {
|
||||
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.SetUnstructuredContent(data)
|
||||
u.SetUnstructuredContent(dataMap)
|
||||
ctx.Element = u
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -31,9 +31,9 @@ import (
|
|||
"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
|
||||
var wildCardAllowedVariables = regexp.MustCompile(`\{\{\s*(request\.|serviceAccountName|serviceAccountNamespace)[^{}]*\}\}`)
|
||||
|
|
|
@ -3,7 +3,6 @@ policies:
|
|||
- policies.yaml
|
||||
resources:
|
||||
- resources.yaml
|
||||
variables: variables.yaml
|
||||
results:
|
||||
- policy: validate-empty-dir-mountpath
|
||||
rule: check-mount-paths
|
||||
|
@ -40,3 +39,18 @@ results:
|
|||
resource: test-pod-with-resources-multiple-ctnrs
|
||||
kind: Pod
|
||||
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: "?*"
|
||||
limits:
|
||||
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
|
||||
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:
|
||||
name: test-pod2
|
||||
spec:
|
||||
|
|
Loading…
Reference in a new issue