1
0
Fork 0
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:
Sambhav Kothari 2022-05-02 17:39:37 +01:00 committed by GitHub
parent 80abda568e
commit 0a5f004047
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 17 deletions

View file

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

View file

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

View file

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

View file

@ -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)[^{}]*\}\}`)

View file

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

View file

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

View file

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