2022-01-04 17:36:33 -08:00
package patch
2020-09-01 21:42:05 +05:30
import (
2021-07-23 20:53:37 +03:00
"encoding/json"
"fmt"
"github.com/go-logr/logr"
2022-01-04 17:36:33 -08:00
"github.com/kyverno/kyverno/pkg/engine/anchor"
2021-07-23 20:53:37 +03:00
"github.com/kyverno/kyverno/pkg/engine/validate"
2022-05-17 07:56:48 +02:00
"github.com/pkg/errors"
2022-01-04 17:36:33 -08:00
"sigs.k8s.io/kustomize/kyaml/yaml"
2020-09-01 21:42:05 +05:30
)
2021-07-23 20:53:37 +03:00
type ConditionError struct {
errorChain error
}
func ( ce ConditionError ) Error ( ) string {
2021-09-13 18:59:28 +03:00
return fmt . Sprintf ( "condition failed: %s" , ce . errorChain . Error ( ) )
2021-07-23 20:53:37 +03:00
}
func NewConditionError ( err error ) error {
return ConditionError { err }
}
2021-09-13 18:59:28 +03:00
type GlobalConditionError struct {
errorChain error
}
func ( ce GlobalConditionError ) Error ( ) string {
return fmt . Sprintf ( "global condition failed: %s" , ce . errorChain . Error ( ) )
}
func NewGlobalConditionError ( err error ) error {
return GlobalConditionError { err }
}
2020-09-01 21:42:05 +05:30
// 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".
2021-07-23 20:53:37 +03:00
// A parent node having Sequence keeps the data as array of Node inside Content field and a Tag field as "!!seq".
2020-09-01 21:42:05 +05:30
// https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/yaml/rnode.go
2021-07-23 20:53:37 +03:00
func preProcessPattern ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
err := preProcessRecursive ( logger , pattern , resource )
if err != nil {
return err
}
return deleteConditionElements ( pattern )
}
func preProcessRecursive ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2020-09-01 21:42:05 +05:30
switch pattern . YNode ( ) . Kind {
case yaml . MappingNode :
2021-07-23 20:53:37 +03:00
return walkMap ( logger , pattern , resource )
2020-09-01 21:42:05 +05:30
case yaml . SequenceNode :
2021-07-23 20:53:37 +03:00
return walkList ( logger , pattern , resource )
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
func walkMap ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2022-01-23 05:54:22 -08:00
if _ , err := handleAddIfNotPresentAnchor ( pattern , resource ) ; err != nil {
return errors . Wrap ( err , "failed to process addIfNotPresent anchor" )
2021-07-23 20:53:37 +03:00
}
2020-09-01 21:42:05 +05:30
2022-01-23 05:54:22 -08:00
if err := validateConditions ( logger , pattern , resource ) ; err != nil {
return err // do not wrap condition errors
2020-09-01 21:42:05 +05:30
}
2022-01-23 05:54:22 -08:00
isNotAnchor := func ( key string ) bool {
2021-09-13 18:59:28 +03:00
return ! hasAnchor ( key )
2022-01-23 05:54:22 -08:00
}
nonAnchors , err := filterKeys ( pattern , isNotAnchor )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-09-13 18:59:28 +03:00
for _ , field := range nonAnchors {
2022-01-23 05:54:22 -08:00
var resourceValue * yaml . RNode
if resource != nil && resource . Field ( field ) != nil {
2021-09-13 18:59:28 +03:00
resourceValue = resource . Field ( field ) . Value
2021-07-23 20:53:37 +03:00
}
2021-09-13 18:59:28 +03:00
err := preProcessRecursive ( logger , pattern . Field ( field ) . Value , resourceValue )
2021-07-23 20:53:37 +03:00
if err != nil {
return err
2020-09-01 21:42:05 +05:30
}
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
func walkList ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
elements , err := pattern . Elements ( )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
if len ( elements ) == 0 {
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
if elements [ 0 ] . YNode ( ) . Kind == yaml . MappingNode {
return processListOfMaps ( logger , pattern , resource )
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
// processListOfMaps - process arrays
2020-09-01 21:42:05 +05:30
// in many cases like containers, volumes kustomize uses name field to match resource for processing
2021-07-23 20:53:37 +03:00
// If any conditional anchor match resource field and if the pattern doesn't contain "name" field and
// resource contains "name" field, then copy the name field from resource to pattern.
func processListOfMaps ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2020-09-01 21:42:05 +05:30
patternElements , err := pattern . Elements ( )
if err != nil {
return err
}
resourceElements , err := resource . Elements ( )
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
for _ , patternElement := range patternElements {
2021-07-23 20:53:37 +03:00
// If pattern has conditions, look for matching elements and process them
2021-09-13 18:59:28 +03:00
hasAnyAnchor := hasAnchors ( patternElement , hasAnchor )
hasGlobalConditions := hasAnchors ( patternElement , anchor . IsGlobalAnchor )
if hasAnyAnchor {
anyGlobalConditionPassed := false
var lastGlobalAnchorError error = nil
2020-09-01 21:42:05 +05:30
for _ , resourceElement := range resourceElements {
2022-01-23 05:54:22 -08:00
if err := preProcessRecursive ( logger , patternElement , resourceElement ) ; err != nil {
logger . V ( 3 ) . Info ( "anchor mismatch" , "reason" , err . Error ( ) )
if isConditionError ( err ) {
2021-07-23 20:53:37 +03:00
continue
}
2022-01-23 05:54:22 -08:00
if isGlobalConditionError ( err ) {
lastGlobalAnchorError = err
2021-09-13 18:59:28 +03:00
continue
}
2022-01-23 05:54:22 -08:00
return err
}
2021-07-23 20:53:37 +03:00
2022-01-23 05:54:22 -08:00
if hasGlobalConditions {
// global anchor has passed, there is no need to return an error
anyGlobalConditionPassed = true
} else {
if err := handlePatternName ( pattern , patternElement , resourceElement ) ; err != nil {
return errors . Wrap ( err , "failed to update name in pattern" )
2020-09-01 21:42:05 +05:30
}
}
}
2021-09-13 18:59:28 +03:00
if ! anyGlobalConditionPassed && lastGlobalAnchorError != nil {
return lastGlobalAnchorError
}
2020-09-01 21:42:05 +05:30
}
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2022-01-23 05:54:22 -08:00
func handlePatternName ( pattern , patternElement , resourceElement * yaml . RNode ) error {
// If condition is satisfied, create new pattern list element based on patternElement
// but related with current resource element by name.
// Resource element must have name. Without name kustomize won't be able to update this element.
// In case if element does not have name, skip it.
resourceElementName := resourceElement . Field ( "name" )
if resourceElementName . IsNilOrEmpty ( ) {
return nil
}
newNode := patternElement . Copy ( )
empty , err := deleteAnchors ( newNode , true , false )
if err != nil {
return err
}
// Do not add an empty element to the patch
if empty {
return nil
}
err = newNode . PipeE ( yaml . SetField ( "name" , resourceElementName . Value ) )
if err != nil {
return err
}
err = pattern . PipeE ( yaml . Append ( newNode . YNode ( ) ) )
if err != nil {
return err
}
return nil
}
func isConditionError ( err error ) bool {
_ , ok := err . ( ConditionError )
return ok
}
func isGlobalConditionError ( err error ) bool {
_ , ok := err . ( GlobalConditionError )
return ok
}
2021-07-23 20:53:37 +03:00
// validateConditions checks all conditions from current map.
// If at least one condition fails, return error.
// If caller handles list of maps and gets an error, it must skip element.
2021-09-13 18:59:28 +03:00
// If caller handles list of maps and gets GlobalConditionError, it must skip entire rule.
2021-07-23 20:53:37 +03:00
// If caller handles map, it must stop processing and skip entire rule.
func validateConditions ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2021-09-13 18:59:28 +03:00
var err error
err = validateConditionsInternal ( logger , pattern , resource , anchor . IsGlobalAnchor )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-09-13 18:59:28 +03:00
return NewGlobalConditionError ( err )
2020-09-01 21:42:05 +05:30
}
2021-01-08 16:45:39 -08:00
2021-09-13 18:59:28 +03:00
err = validateConditionsInternal ( logger , pattern , resource , anchor . IsConditionAnchor )
if err != nil {
return NewConditionError ( err )
2021-01-08 16:45:39 -08:00
}
2020-09-01 21:42:05 +05:30
return nil
}
2022-01-23 05:54:22 -08:00
// handleAddIfNotPresentAnchor handles adding anchors.
2021-07-23 20:53:37 +03:00
// Remove anchor from pattern, if field already exists.
// Remove anchor wrapping from key, if field does not exist in the resource.
2022-01-23 05:54:22 -08:00
func handleAddIfNotPresentAnchor ( pattern , resource * yaml . RNode ) ( int , error ) {
anchors , err := filterKeys ( pattern , anchor . IsAddIfNotPresentAnchor )
2020-09-01 21:42:05 +05:30
if err != nil {
2022-01-23 05:54:22 -08:00
return 0 , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2022-01-23 05:54:22 -08:00
for _ , a := range anchors {
key , _ := anchor . RemoveAnchor ( a )
2021-07-23 20:53:37 +03:00
if resource != nil && resource . Field ( key ) != nil {
// Resource already has this field.
2022-01-23 05:54:22 -08:00
// Delete the field with addIfNotPresent anchor from patch.
err = pattern . PipeE ( yaml . Clear ( a ) )
2020-09-01 21:42:05 +05:30
if err != nil {
2022-01-23 05:54:22 -08:00
return 0 , err
2020-09-01 21:42:05 +05:30
}
2022-01-23 05:54:22 -08:00
} else {
// Remove anchor tags from patch field key.
renameField ( a , key , pattern )
2020-09-01 21:42:05 +05:30
}
}
2021-07-23 20:53:37 +03:00
2022-01-23 05:54:22 -08:00
return len ( anchors ) , nil
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
func filterKeys ( pattern * yaml . RNode , condition func ( string ) bool ) ( [ ] string , error ) {
2022-01-23 05:54:22 -08:00
if ! isMappingNode ( pattern ) {
return nil , nil
}
2021-07-23 20:53:37 +03:00
keys := make ( [ ] string , 0 )
fields , err := pattern . Fields ( )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-07-23 20:53:37 +03:00
return keys , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
for _ , key := range fields {
if condition ( key ) {
keys = append ( keys , key )
continue
}
}
return keys , nil
}
2022-01-23 05:54:22 -08:00
func isMappingNode ( node * yaml . RNode ) bool {
if err := yaml . ErrorIfInvalid ( node , yaml . MappingNode ) ; err != nil {
return false
}
return true
}
2021-09-13 18:59:28 +03:00
func hasAnchor ( key string ) bool {
2022-01-23 05:54:22 -08:00
return anchor . ContainsCondition ( key ) || anchor . IsAddIfNotPresentAnchor ( key )
2021-09-13 18:59:28 +03:00
}
func hasAnchors ( pattern * yaml . RNode , isAnchor func ( key string ) bool ) bool {
2022-01-23 05:54:22 -08:00
ynode := pattern . YNode ( )
kind := ynode . Kind
if kind == yaml . MappingNode {
2021-07-23 20:53:37 +03:00
fields , err := pattern . Fields ( )
if err != nil {
return false
}
for _ , key := range fields {
2021-09-13 18:59:28 +03:00
if isAnchor ( key ) {
2021-07-23 20:53:37 +03:00
return true
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
patternNode := pattern . Field ( key )
if ! patternNode . IsNilOrEmpty ( ) {
2021-09-13 18:59:28 +03:00
if hasAnchors ( patternNode . Value , isAnchor ) {
2021-07-23 20:53:37 +03:00
return true
2020-09-01 21:42:05 +05:30
}
}
}
2022-01-23 05:54:22 -08:00
} else if kind == yaml . ScalarNode {
v := ynode . Value
return anchor . ContainsCondition ( v )
} else if kind == yaml . SequenceNode {
elements , _ := pattern . Elements ( )
for _ , e := range elements {
if hasAnchors ( e , isAnchor ) {
return true
}
}
return false
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
return false
}
func renameField ( name , newName string , pattern * yaml . RNode ) {
field := pattern . Field ( name )
if field == nil {
return
}
field . Key . YNode ( ) . Value = newName
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
func convertRNodeToInterface ( document * yaml . RNode ) ( interface { } , error ) {
if document . YNode ( ) . Kind == yaml . ScalarNode {
return document . YNode ( ) . Value , nil
}
rawDocument , err := document . MarshalJSON ( )
if err != nil {
return nil , err
}
var documentInterface interface { }
err = json . Unmarshal ( rawDocument , & documentInterface )
if err != nil {
return nil , err
}
return documentInterface , nil
}
func checkCondition ( logger logr . Logger , pattern * yaml . RNode , resource * yaml . RNode ) error {
2021-09-27 23:40:05 -07:00
patternInterface , err := convertRNodeToInterface ( pattern )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
resourceInterface , err := convertRNodeToInterface ( resource )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
2021-09-30 23:34:04 -07:00
err = validate . MatchPattern ( logger , resourceInterface , patternInterface )
2021-09-27 23:40:05 -07:00
if err != nil {
2021-09-26 18:30:53 -07:00
return err
}
return nil
2021-07-23 20:53:37 +03:00
}
func deleteConditionElements ( pattern * yaml . RNode ) error {
2020-09-01 21:42:05 +05:30
fields , err := pattern . Fields ( )
if err != nil {
2021-07-23 20:53:37 +03:00
return err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
for _ , field := range fields {
2022-01-23 05:54:22 -08:00
deleteScalar := anchor . ContainsCondition ( field )
canDelete , err := deleteAnchors ( pattern . Field ( field ) . Value , deleteScalar , false )
2021-07-23 20:53:37 +03:00
if err != nil {
return err
}
2022-01-23 05:54:22 -08:00
if canDelete {
2021-07-23 20:53:37 +03:00
err = pattern . PipeE ( yaml . Clear ( field ) )
if err != nil {
return err
2020-09-01 21:42:05 +05:30
}
}
}
2021-07-23 20:53:37 +03:00
return nil
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
// deleteAnchors deletes all the anchors and returns true,
// if this node must be deleted from patch.
// Node is considered to be deleted, if there were only
2022-01-23 05:54:22 -08:00
// anchors elements. After anchors elements are removed,
// A patch with nil values which could cause
2021-07-23 20:53:37 +03:00
// unnecessary resource elements deletion.
2022-01-23 05:54:22 -08:00
func deleteAnchors ( node * yaml . RNode , deleteScalar , traverseMappingNodes bool ) ( bool , error ) {
2021-07-23 20:53:37 +03:00
switch node . YNode ( ) . Kind {
2020-09-01 21:42:05 +05:30
case yaml . MappingNode :
2022-01-23 05:54:22 -08:00
return deleteAnchorsInMap ( node , traverseMappingNodes )
2021-07-23 20:53:37 +03:00
case yaml . SequenceNode :
2022-01-23 05:54:22 -08:00
return deleteAnchorsInList ( node , traverseMappingNodes )
case yaml . ScalarNode :
return deleteScalar , nil
2021-07-23 20:53:37 +03:00
}
return false , nil
}
2022-01-23 05:54:22 -08:00
func deleteAnchorsInMap ( node * yaml . RNode , traverseMappingNodes bool ) ( bool , error ) {
2021-09-13 18:59:28 +03:00
conditions , err := filterKeys ( node , anchor . ContainsCondition )
2021-07-23 20:53:37 +03:00
if err != nil {
return false , err
}
2022-01-23 05:54:22 -08:00
// remove all conditional anchors with no child nodes first
anchorsExist := false
2021-07-23 20:53:37 +03:00
for _ , condition := range conditions {
2022-01-23 05:54:22 -08:00
field := node . Field ( condition )
shouldDelete , err := deleteAnchors ( field . Value , true , traverseMappingNodes )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-07-23 20:53:37 +03:00
return false , err
2020-09-01 21:42:05 +05:30
}
2022-01-23 05:54:22 -08:00
if shouldDelete {
if err := node . PipeE ( yaml . Clear ( condition ) ) ; err != nil {
return false , err
}
} else {
anchorsExist = true
}
}
if anchorsExist {
if err := stripAnchorsFromNode ( node , "" ) ; err != nil {
return false , errors . Wrap ( err , "failed to remove anchor tags" )
}
2021-07-23 20:53:37 +03:00
}
2020-09-01 21:42:05 +05:30
2021-07-23 20:53:37 +03:00
fields , err := node . Fields ( )
if err != nil {
return false , err
}
2021-09-13 18:59:28 +03:00
needToDelete := true
2021-07-23 20:53:37 +03:00
for _ , field := range fields {
2022-01-23 05:54:22 -08:00
canDelete , err := deleteAnchors ( node . Field ( field ) . Value , false , traverseMappingNodes )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-07-23 20:53:37 +03:00
return false , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2022-01-23 05:54:22 -08:00
if canDelete {
2021-09-13 18:59:28 +03:00
err = node . PipeE ( yaml . Clear ( field ) )
if err != nil {
return false , err
}
} else {
// If we have at least one element without anchor,
// then we don't need to delete this element.
needToDelete = false
2020-09-01 21:42:05 +05:30
}
}
2021-01-08 16:45:39 -08:00
2021-09-13 18:59:28 +03:00
return needToDelete , nil
2021-07-23 20:53:37 +03:00
}
2021-01-08 16:45:39 -08:00
2022-01-23 05:54:22 -08:00
// stripAnchorFromNode strips one or more anchor fields from the node.
// If key is "" all anchor fields are stripped. Otherwise, only the matching
// field is stripped.
func stripAnchorsFromNode ( node * yaml . RNode , key string ) error {
anchors , err := filterKeys ( node , anchor . ContainsCondition )
if err != nil {
return err
}
for _ , a := range anchors {
k , _ := anchor . RemoveAnchor ( a )
if key == "" || k == key {
renameField ( a , k , node )
}
}
return nil
}
func deleteAnchorsInList ( node * yaml . RNode , traverseMappingNodes bool ) ( bool , error ) {
2021-07-23 20:53:37 +03:00
elements , err := node . Elements ( )
if err != nil {
return false , err
2021-01-08 16:45:39 -08:00
}
2021-09-13 18:59:28 +03:00
wasEmpty := len ( elements ) == 0
2021-07-23 20:53:37 +03:00
for i , element := range elements {
2021-09-13 18:59:28 +03:00
if hasAnchors ( element , hasAnchor ) {
2022-01-23 05:54:22 -08:00
shouldDelete := true
if traverseMappingNodes && isMappingNode ( element ) {
shouldDelete , err = deleteAnchors ( element , true , traverseMappingNodes )
if err != nil {
return false , errors . Wrap ( err , "failed to delete anchors" )
}
}
if shouldDelete {
deleteListElement ( node , i )
}
2021-07-23 20:53:37 +03:00
} else {
// This element also could have some conditions
// inside sub-arrays. Delete them too.
2022-01-23 05:54:22 -08:00
canDelete , err := deleteAnchors ( element , false , traverseMappingNodes )
2021-01-08 16:45:39 -08:00
if err != nil {
2022-01-23 05:54:22 -08:00
return false , errors . Wrap ( err , "failed to delete anchors" )
2021-01-08 16:45:39 -08:00
}
2022-01-23 05:54:22 -08:00
if canDelete {
2021-07-23 20:53:37 +03:00
deleteListElement ( node , i )
2021-01-08 16:45:39 -08:00
}
}
}
2021-07-23 20:53:37 +03:00
elements , err = node . Elements ( )
if err != nil {
return false , err
}
2021-09-13 18:59:28 +03:00
if len ( elements ) == 0 && ! wasEmpty {
2021-07-23 20:53:37 +03:00
return true , nil
}
return false , nil
}
func deleteListElement ( list * yaml . RNode , i int ) {
content := list . YNode ( ) . Content
list . YNode ( ) . Content = append ( content [ : i ] , content [ i + 1 : ] ... )
2021-01-08 16:45:39 -08:00
}
2021-09-13 18:59:28 +03:00
func validateConditionsInternal ( logger logr . Logger , pattern , resource * yaml . RNode , filter func ( string ) bool ) error {
conditions , err := filterKeys ( pattern , filter )
if err != nil {
return err
}
for _ , condition := range conditions {
2022-01-04 17:36:33 -08:00
conditionKey , _ := anchor . RemoveAnchor ( condition )
2021-09-13 18:59:28 +03:00
if resource == nil || resource . Field ( conditionKey ) == nil {
return fmt . Errorf ( "could not found \"%s\" key in the resource" , conditionKey )
}
2022-01-23 05:54:22 -08:00
patternValue := pattern . Field ( condition ) . Value
resourceValue := resource . Field ( conditionKey ) . Value
if count , err := handleAddIfNotPresentAnchor ( patternValue , resourceValue ) ; err != nil {
return err
} else if count > 0 {
continue
}
if err := checkCondition ( logger , patternValue , resourceValue ) ; err != nil {
2021-09-13 18:59:28 +03:00
return err
}
}
return nil
}