1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-30 19:35:06 +00:00

conditional anchor preprocessing for patch strategic merge (#1090)

* conditional anchor preprocessing for patch strategic merge

* modified sequence pre processing and added unit test

* merged master

* go fmt

* corrected mistake and added error handling to policy validate
This commit is contained in:
Mohan B E 2020-09-01 21:42:05 +05:30 committed by GitHub
parent e0f617b383
commit 3690bf5fff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 603 additions and 29 deletions

5
go.mod
View file

@ -38,7 +38,7 @@ require (
google.golang.org/appengine v1.6.5 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
gotest.tools v2.2.0+incompatible
k8s.io/api v0.17.4
k8s.io/apiextensions-apiserver v0.17.4
@ -48,9 +48,8 @@ require (
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6
sigs.k8s.io/controller-runtime v0.5.0
sigs.k8s.io/kustomize v2.0.3+incompatible
sigs.k8s.io/kustomize/api v0.5.1
sigs.k8s.io/kustomize/kyaml v0.4.1
sigs.k8s.io/kustomize/kyaml v0.6.1
sigs.k8s.io/yaml v1.2.0
)

7
go.sum
View file

@ -910,6 +910,7 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -1055,6 +1056,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -1266,6 +1268,8 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -1318,12 +1322,15 @@ mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZI
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/controller-runtime v0.5.0 h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo=
sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8=
sigs.k8s.io/kustomize v1.0.11 h1:Yb+6DDt9+aR2AvQApvUaKS/ugteeG4MPyoFeUHiPOjk=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/api v0.5.1 h1:iHGTs5LcnJGqHstUSxWD/kX6XZgmd82x79LLlZwDU0I=
sigs.k8s.io/kustomize/api v0.5.1/go.mod h1:LGqJ9ZWOnWDqlECqrFgNUyEqSJc6ooA9ZiWZ4KFZv+I=
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
sigs.k8s.io/kustomize/kyaml v0.6.1 h1:mwffj5vt3MPdbWV3fZnnwol8SO7sUoGdgejBlvseyak=
sigs.k8s.io/kustomize/kyaml v0.6.1/go.mod h1:bEzbO5pN9OvlEeCLvFHo8Pu7SA26Herc2m60UeWZBdI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=

View file

@ -109,7 +109,7 @@ func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapI
if excludeGroupRole != "" {
cd.log.Info("init configuration from commandline arguments for excludeGroupRole")
cd.initRbac("excludeRoles", excludeGroupRole)
}else{
} else {
cd.initRbac("excludeRoles", "")
}
@ -187,9 +187,9 @@ func (cd *ConfigData) load(cm v1.ConfigMap) {
defer cd.mux.Unlock()
// get resource filters
filters, ok := cm.Data["resourceFilters"]
if !ok {
if !ok {
logger.V(4).Info("configuration: No resourceFilters defined in ConfigMap")
}else{
} else {
newFilters := parseKinds(filters)
if reflect.DeepEqual(newFilters, cd.filters) {
logger.V(4).Info("resourceFilters did not change")
@ -202,7 +202,7 @@ func (cd *ConfigData) load(cm v1.ConfigMap) {
// get resource filters
excludeGroupRole, ok := cm.Data["excludeGroupRole"]
if !ok {
if !ok {
logger.V(4).Info("configuration: No excludeGroupRole defined in ConfigMap")
}
newExcludeGroupRoles := parseRbac(excludeGroupRole)
@ -217,9 +217,9 @@ func (cd *ConfigData) load(cm v1.ConfigMap) {
// get resource filters
excludeUsername, ok := cm.Data["excludeUsername"]
if !ok {
if !ok {
logger.V(4).Info("configuration: No excludeUsername defined in ConfigMap")
}else{
} else {
excludeUsernames := parseRbac(excludeUsername)
if reflect.DeepEqual(excludeUsernames, cd.excludeUsername) {
logger.V(4).Info("excludeGroupRole did not change")

View file

@ -3,7 +3,9 @@ package mutate
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"time"
"github.com/go-logr/logr"
@ -27,6 +29,26 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
logger.V(4).Info("finished applying strategicMerge patch", "processingTime", resp.RuleStats.ProcessingTime.String())
}()
// ====== Meet Conditions =======
if path, overlayerr := meetConditions(log, resource.UnstructuredContent(), overlay); !reflect.DeepEqual(overlayerr, overlayError{}) {
switch overlayerr.statusCode {
// anchor key does not exist in the resource, skip applying policy
case conditionNotPresent:
log.V(4).Info("skip applying policy", "path", path, "error", overlayerr)
log.V(3).Info("skip applying rule", "reason", "conditionNotPresent")
resp.Success = true
return resp, resource
// anchor key is not satisfied in the resource, skip applying policy
case conditionFailure:
log.V(4).Info("failed to validate condition", "path", path, "error", overlayerr)
log.V(3).Info("skip applying rule", "reason", "conditionFailure")
resp.Success = true
resp.Message = overlayerr.ErrorMsg()
return resp, resource
}
}
// ============================
overlayBytes, err := json.Marshal(overlay)
if err != nil {
resp.Success = false
@ -42,7 +64,6 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
return resp, resource
}
patchedBytes, err := strategicMergePatch(string(base), string(overlayBytes))
if err != nil {
msg := fmt.Sprintf("failed to apply patchStrategicMerge: %v", err)
@ -78,14 +99,26 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
}
func strategicMergePatch(base, overlay string) ([]byte, error) {
patch := yaml.MustParse(overlay)
patch := yaml.MustParse(overlay)
preprocessedYaml, err := preProcessStrategicMergePatch(overlay, base)
if err != nil {
return []byte{}, errors.New(fmt.Sprintf("failed to preProcess rule : %+v", err))
}
patch = preprocessedYaml
f := patchstrategicmerge.Filter{
Patch: patch,
}
baseObj := buffer{Buffer: bytes.NewBufferString(base)}
err := filtersutil.ApplyToJSON(f, baseObj)
err = filtersutil.ApplyToJSON(f, baseObj)
return baseObj.Bytes(), err
}
func preProcessStrategicMergePatch(pattern, resource string) (*yaml.RNode, error) {
patternNode := yaml.MustParse(pattern)
resourceNode := yaml.MustParse(resource)
err := preProcessPattern(patternNode, resourceNode)
return patternNode, err
}

View file

@ -0,0 +1,506 @@
package mutate
import (
"github.com/minio/minio/pkg/wildcard"
anchor "github.com/nirmata/kyverno/pkg/engine/anchor/common"
yaml "sigs.k8s.io/kustomize/kyaml/yaml"
)
// preProcessPattern - Dynamically preProcess the yaml
// 1> For conditional anchor remove anchors from the pattern.
// 2> For Adding anchors remove anchor tags.
// The whole yaml is structured as a pointer tree.
// https://godoc.org/gopkg.in/yaml.v3#Node
// A single Node contains Tag to identify it as MappingNode (map[string]interface{}), Sequence ([]interface{}), ScalarNode (string, int, float bool etc.)
// A parent node having MappingNode keeps the data as <keyNode>, <ValueNode> inside it's Content field and Tag field as "!!map".
// A parent node having MappingNode keeps the data as array of Node inside Content field and a Tag field as "!!seq".
// https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/yaml/rnode.go
func preProcessPattern(pattern, resource *yaml.RNode) error {
switch pattern.YNode().Kind {
case yaml.MappingNode:
err := walkMap(pattern, resource)
if err != nil {
return err
}
case yaml.SequenceNode:
err := walkArray(pattern, resource)
if err != nil {
return err
}
case yaml.ScalarNode:
if pattern.YNode().Value != resource.YNode().Value {
if wildcard.Match(pattern.YNode().Value, resource.YNode().Value) {
}
}
}
return nil
}
// getIndex - get the index of the key from the fields.
var getIndex = func(k string, list []string) int {
for i, v := range list {
if v == k {
return 2 * i
}
}
return -1
}
// removeAnchorNode - removes anchor nodes from yaml
func removeAnchorNode(targetNode *yaml.RNode, index int) {
targetNode.YNode().Content = append(targetNode.YNode().Content[:index], targetNode.YNode().Content[index+2:]...)
}
func removeKeyFromFields(key string, fields []string) []string {
for i, v := range fields {
if v == key {
return append(fields[:i], fields[i+1:]...)
}
}
return fields
}
// walkMap - walk through the MappingNode
/* 1> For conditional anchor remove anchors from the pattern, patchStrategicMerge will add the anchors as a new patch,
so it is necessary to remove the anchor mapsfrom the pattern before calling patchStrategicMerge.
| (volumes):
| - (hostPath):
| path: "/var/run/docker.sock"
walkMap will remove the node containing (volumes) from the yaml
*/
/* 2> For Adding anchors remove anchor tags.
annotations:
- "+(annotation1)": "atest1"
will remove "+(" and ")" chars from pattern.
*/
func walkMap(pattern, resource *yaml.RNode) error {
sfields, fields, err := getAnchorSortedFields(pattern)
if err != nil {
return err
}
for _, key := range sfields {
if anchor.IsConditionAnchor(key) {
// remove anchor node from yaml
// In a MappingNode, yaml.Node store <keyNode>:<valueNode> pairs as an array of Node inside Content field,
// <valueNode> further can be a MappingNode, SequenceNode or ScalarNode.
// for a mapping node with single key value pair then key is in position index 0 and value in position 1 and
// the next <keyNode>:<valueNode> pairs in position 2 and 3 respectively.
ind := getIndex(key, fields)
if ind == -1 {
continue
}
// remove anchor from the map and update fields
removeAnchorNode(pattern, ind)
sfields = removeKeyFromFields(key, sfields)
fields = removeKeyFromFields(key, fields)
continue
}
if anchor.IsAddingAnchor(key) {
ind := getIndex(key, fields)
if ind == -1 {
continue
}
// remove anchor tags from value
// A MappingNode contains keyNode and Value node
// keyNode contains it's key value in it's Value field, So remove anchor tags from Value field
pattern.YNode().Content[ind].Value = removeAnchor(key)
}
noAnchorKey := removeAnchor(key)
patternMapNode := pattern.Field(noAnchorKey)
resourceMapNode := resource.Field(noAnchorKey)
if resourceMapNode != nil {
if !patternMapNode.IsNilOrEmpty() {
err := preProcessPattern(patternMapNode.Value, resourceMapNode.Value)
if err != nil {
return err
}
}
}
}
return nil
}
// walkArray - walk through array elements
// 1> processNonAssocSequence - process array of basic types. Ex:- {command: ["ls", "ls -l"]}
// 2> processAssocSequence - process array having MappingNode. like containers, volumes etc.
func walkArray(pattern, resource *yaml.RNode) error {
pafs, err := pattern.Elements()
if err != nil {
return err
}
if len(pafs) == 0 {
return nil
}
switch pafs[0].YNode().Kind {
case yaml.MappingNode:
return processAssocSequence(pattern, resource)
case yaml.ScalarNode:
return processNonAssocSequence(pattern, resource)
}
return nil
}
// processAssocSequence - process arrays
// in many cases like containers, volumes kustomize uses name field to match resource for processing
// 1> If any conditional anchor match resource field and if the pattern doesn't contains "name" field and
// resource contains "name" field then copy the name field from resource to pattern.
// 2> If the resource doesn't contains "name" field then just remove anchor field from yaml.
/*
Policy:
"spec": {
"containers": [{
"(image)": "*:latest",
"imagePullPolicy": "Always"
}]}
Resource:
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"imagePullPolicy": "Never"
}]
}
After Preprocessing:
"spec": {
"containers": [{
"name": "nginx",
"imagePullPolicy": "Always"
}]}
kustomize uses name field to match resource for processing. So if containers doesn't contains name field then it will be skipped.
So if a conditional anchor image matches resouce then remove "(image)" field from yaml and add the matching names from the resource.
*/
func processAssocSequence(pattern, resource *yaml.RNode) error {
patternElements, err := pattern.Elements()
if err != nil {
return err
}
for _, patternElement := range patternElements {
if hasAnchors(patternElement) {
err := processAnchorSequence(patternElement, resource, pattern)
if err != nil {
return err
}
}
}
// remove the sequence with anchors
err = removeAnchorSequence(pattern, resource)
if err != nil {
return err
}
return preProcessArrayPattern(pattern, resource)
}
func preProcessArrayPattern(pattern, resource *yaml.RNode) error {
patternElements, err := pattern.Elements()
if err != nil {
return err
}
resourceElements, err := resource.Elements()
if err != nil {
return err
}
for _, patternElement := range patternElements {
patternNameField := patternElement.Field("name")
if patternNameField != nil {
patternNameValue, err := patternNameField.Value.String()
if err != nil {
return err
}
for _, resourceElement := range resourceElements {
resourceNameField := resourceElement.Field("name")
if resourceNameField != nil {
resourceNameValue, err := resourceNameField.Value.String()
if err != nil {
return err
}
if patternNameValue == resourceNameValue {
err := preProcessPattern(patternElement, resourceElement)
if err != nil {
return err
}
}
}
}
}
}
return nil
}
/*
removeAnchorSequence :- removes sequence containing conditional anchor
Pattern:
"spec": {
"containers": [{
"(image)": "*:latest",
"imagePullPolicy": "Always"
},
{
"name": "nginx",
"imagePullPolicy": "Always"
}]}
After Removing Conditional Sequence:
"spec": {
"containers": [{
"name": "nginx",
"imagePullPolicy": "Always"
}]}
*/
func removeAnchorSequence(pattern, resource *yaml.RNode) error {
patternElements, err := pattern.Elements()
if err != nil {
return err
}
for index, patternElement := range patternElements {
if hasAnchors(patternElement) {
sfields, _, err := getAnchorSortedFields(patternElement)
if err != nil {
return err
}
for _, key := range sfields {
if anchor.IsConditionAnchor(key) {
pattern.YNode().Content = append(pattern.YNode().Content[:index], pattern.YNode().Content[index+1:]...)
}
}
}
}
return nil
}
func processAnchorSequence(pattern, resource, arrayPattern *yaml.RNode) error {
resourceElements, err := resource.Elements()
if err != nil {
return err
}
switch pattern.YNode().Kind {
case yaml.MappingNode:
for _, resourceElement := range resourceElements {
err := processAnchorMap(pattern, resourceElement, arrayPattern)
if err != nil {
return err
}
}
}
return nil
}
// processAnchorMap - process arrays
// in many cases like containers, volumes kustomize uses name field to match resource for processing
// 1> If any conditional anchor match resource field and if the pattern doesn't contains "name" field and
// resource contains "name" field then copy the name field from resource to pattern.
// 2> If the resource doesn't contains "name" field then just remove anchor field from yaml.
/*
Policy:
"spec": {
"containers": [{
"(image)": "*:latest",
"imagePullPolicy": "Always"
}]}
Resource:
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"imagePullPolicy": "Never"
}]
}
After Preprocessing:
"spec": {
"containers": [{
"(image)": "*:latest",
"imagePullPolicy": "Always"
},
{
"name": "nginx",
"imagePullPolicy": "Always"
}]}
kustomize uses name field to match resource for processing. So if containers doesn't contains name field then it will be skipped.
So if a conditional anchor image matches resouce then remove "(image)" field from yaml and add the matching names from the resource.
*/
func processAnchorMap(pattern, resource, arrayPattern *yaml.RNode) error {
sfields, fields, err := getAnchorSortedFields(pattern)
if err != nil {
return err
}
for _, key := range sfields {
if anchor.IsConditionAnchor(key) {
_, efields, err := getAnchorSortedFields(resource)
if err != nil {
return err
}
noAnchorKey := removeAnchor(key)
eind := getIndex("name", efields)
if eind != -1 && getIndex("name", fields) == -1 {
patternMapNode := pattern.Field(key)
resourceMapNode := resource.Field(noAnchorKey)
if resourceMapNode != nil {
pval, err := patternMapNode.Value.String()
if err != nil {
return err
}
eval, err := resourceMapNode.Value.String()
if err != nil {
return err
}
if wildcard.Match(pval, eval) {
newNodeString, err := pattern.String()
if err != nil {
return err
}
newNode, err := yaml.Parse(newNodeString)
if err != nil {
return err
}
for i, ekey := range efields {
if ekey == noAnchorKey {
pind := getIndex(key, fields)
if pind == -1 {
continue
}
removeAnchorNode(newNode, pind)
sfields = removeKeyFromFields(key, sfields)
fields = removeKeyFromFields(key, fields)
if ekey == "name" {
newNode.YNode().Content = append(newNode.YNode().Content, resource.YNode().Content[2*i])
newNode.YNode().Content = append(newNode.YNode().Content, resource.YNode().Content[2*i+1])
}
} else if ekey == "name" {
newNode.YNode().Content = append(newNode.YNode().Content, resource.YNode().Content[2*i])
newNode.YNode().Content = append(newNode.YNode().Content, resource.YNode().Content[2*i+1])
}
}
arrayPattern.YNode().Content = append(arrayPattern.YNode().Content, newNode.YNode())
}
}
} else {
ind := getIndex(key, fields)
if ind == -1 {
continue
}
removeAnchorNode(pattern, ind)
sfields = removeKeyFromFields(key, sfields)
fields = removeKeyFromFields(key, fields)
}
continue
}
}
return nil
}
func processNonAssocSequence(pattern, resource *yaml.RNode) error {
pafs, err := pattern.Elements()
if err != nil {
return err
}
rafs, err := resource.Elements()
if err != nil {
return err
}
for _, sa := range rafs {
des, err := sa.String()
if err != nil {
return err
}
ok := false
for _, ra := range pafs {
src, err := ra.String()
if err != nil {
return err
}
if des == src {
ok = true
break
}
}
if !ok {
pattern.YNode().Content = append(pattern.YNode().Content, sa.YNode())
}
}
return nil
}
// getAnchorSortedFields - get all the keys from a MappingNode sorted by anchor field
func getAnchorSortedFields(pattern *yaml.RNode) ([]string, []string, error) {
anchors := make([]string, 0)
nonAnchors := make([]string, 0)
nestedAnchors := make([]string, 0)
fields, err := pattern.Fields()
if err != nil {
return fields, fields, err
}
for _, key := range fields {
if anchor.IsConditionAnchor(key) {
anchors = append(anchors, key)
continue
}
patternMapNode := pattern.Field(key)
if !patternMapNode.IsNilOrEmpty() {
if hasAnchors(patternMapNode.Value) {
nestedAnchors = append(nestedAnchors, key)
continue
}
}
nonAnchors = append(nonAnchors, key)
}
anchors = append(anchors, nestedAnchors...)
return append(anchors, nonAnchors...), fields, nil
}
func hasAnchors(pattern *yaml.RNode) bool {
switch pattern.YNode().Kind {
case yaml.MappingNode:
fields, err := pattern.Fields()
if err != nil {
return false
}
for _, key := range fields {
if anchor.IsConditionAnchor(key) {
return true
}
patternMapNode := pattern.Field(key)
if !patternMapNode.IsNilOrEmpty() {
if hasAnchors(patternMapNode.Value) {
return true
}
}
}
case yaml.SequenceNode:
pafs, err := pattern.Elements()
if err != nil {
return false
}
for _, pa := range pafs {
if hasAnchors(pa) {
return true
}
}
}
return false
}
func copyData(resource *yaml.RNode) (*yaml.RNode, error) {
newNodeString, err := resource.String()
if err != nil {
return resource, err
}
newNode, err := yaml.Parse(newNodeString)
return newNode, err
}

View file

@ -0,0 +1,26 @@
package mutate
import (
assertnew "github.com/stretchr/testify/assert"
"gotest.tools/assert"
"regexp"
"strings"
"testing"
)
func Test_preProcessStrategicMergePatch(t *testing.T) {
rawPolicy := []byte(`{"metadata":{"+(annotations)":{"+(annotation1)":"atest1"},"labels":{"+(label1)":"test1"}},"spec":{"(volumes)":[{"(hostPath)":{"path":"/var/run/docker.sock"}}],"containers":[{"(image)":"*:latest","command":["ls"],"imagePullPolicy":"Always"}]}}`)
rawResource := []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{"annotation1":"atest2"},"labels":{"label1":"test2","label2":"test2"},"name":"check-root-user"},"spec":{"containers":[{"command":["ll"],"image":"nginx:latest","imagePullPolicy":"Never","name":"nginx"},{"image":"busybox:latest","imagePullPolicy":"Never","name":"busybox"}],"volumes":[{"hostPath":{"path":"/var/run/docker.sock"},"name":"test-volume"}]}}`)
expected := `{"metadata": {"annotations": {"annotation1": "atest1"}, "labels": {"label1": "test1"}},"spec": {"containers": [{"command": ["ls", "ll"], "imagePullPolicy": "Always", "name": "nginx"},{"command": ["ls"], "imagePullPolicy": "Always", "name": "busybox"}]}}`
preProcessedPolicy, err := preProcessStrategicMergePatch(string(rawPolicy), string(rawResource))
assert.NilError(t, err)
output, err := preProcessedPolicy.String()
assert.NilError(t, err)
re := regexp.MustCompile("\\n")
if !assertnew.Equal(t, strings.ReplaceAll(expected, " ", ""), strings.ReplaceAll(re.ReplaceAllString(output, ""), " ", "")) {
t.FailNow()
}
}

View file

@ -28,7 +28,7 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro
return fmt.Errorf("failed to unmarshal policy admission request err %v", err)
}
if common.PolicyHasVariables(p) && common.PolicyHasNonAllowedVariables(p){
if common.PolicyHasVariables(p) && common.PolicyHasNonAllowedVariables(p) {
return fmt.Errorf("policy contains non allowed variables")
}
@ -55,27 +55,30 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro
// validate Cluster Resources in namespaced cluster policy
// For namespaced cluster policy, ClusterResource type field and values are not allowed in match and exclude
if !mock && p.ObjectMeta.Namespace != "" {
var Empty struct{}
clusterResourcesMap := make(map[string]*struct{})
// Get all the cluster type kind supported by cluster
res, _ := client.GetDiscoveryCache().ServerPreferredResources()
for _, resList := range res {
for _, r := range resList.APIResources {
if r.Namespaced == false {
if clusterResourcesMap[r.Kind] != nil {
clusterResourcesMap[r.Kind] = &Empty
}
var Empty struct{}
clusterResourcesMap := make(map[string]*struct{})
// Get all the cluster type kind supported by cluster
res, err := client.GetDiscoveryCache().ServerPreferredResources()
if err != nil {
return err
}
for _, resList := range res {
for _, r := range resList.APIResources {
if r.Namespaced == false {
if clusterResourcesMap[r.Kind] != nil {
clusterResourcesMap[r.Kind] = &Empty
}
}
}
clusterResources := make([]string, 0, len(clusterResourcesMap))
for k := range clusterResourcesMap {
clusterResources = append(clusterResources, k)
}
return checkClusterResourceInMatchAndExclude(rule, clusterResources)
}
clusterResources := make([]string, 0, len(clusterResourcesMap))
for k := range clusterResourcesMap {
clusterResources = append(clusterResources, k)
}
return checkClusterResourceInMatchAndExclude(rule, clusterResources)
}
if doesMatchAndExcludeConflict(rule) {
return fmt.Errorf("path: spec.rules[%v]: rule is matching an empty set", rule.Name)
}