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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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