package mutate import ( "fmt" "reflect" "strconv" "github.com/go-logr/logr" commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor/common" "github.com/kyverno/kyverno/pkg/engine/validate" "sigs.k8s.io/controller-runtime/pkg/log" ) func meetConditions(log logr.Logger, resource, overlay interface{}) (string, overlayError) { return checkConditions(log, resource, overlay, "/") } // resource and overlay should be the same type func checkConditions(log logr.Logger, resource, overlay interface{}, path string) (string, overlayError) { // overlay has no anchor, return true if !hasNestedAnchors(overlay) { return "", overlayError{} } // resource item exists but has different type // return false if anchor exists in overlay // condition never be true in this case if reflect.TypeOf(resource) != reflect.TypeOf(overlay) { if hasNestedAnchors(overlay) { log.V(4).Info(fmt.Sprintf("element type mismatch at path %s: overlay %T, resource %T", path, overlay, resource)) return path, newOverlayError(conditionFailure, fmt.Sprintf("element type mismatch at path %s: overlay %T %v, resource %T %v", path, overlay, overlay, resource, resource)) } return "", overlayError{} } switch typedOverlay := overlay.(type) { case map[string]interface{}: typedResource := resource.(map[string]interface{}) return checkConditionOnMap(typedResource, typedOverlay, path) case []interface{}: typedResource := resource.([]interface{}) return checkConditionOnArray(typedResource, typedOverlay, path) default: // anchor on non map/array is invalid: // - anchor defined on values log.Info("Found invalid conditional anchor: anchor defined on values") return "", overlayError{} } } func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}, path string) (string, overlayError) { anchors, overlayWithoutAnchor := getAnchorAndElementsFromMap(overlayMap) // validate resource with conditions if newPath, err := validateConditionAnchorMap(resourceMap, anchors, path); !reflect.DeepEqual(err, overlayError{}) { return newPath, err } // traverse overlay pattern to further validate conditions if newPath, err := validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor, path); !reflect.DeepEqual(err, overlayError{}) { return newPath, err } // empty overlayMap return "", overlayError{} } func checkConditionOnArray(resource, overlay []interface{}, path string) (string, overlayError) { if 0 == len(overlay) { log.Log.V(4).Info("Mutate overlay pattern is empty", "path", path) return "", overlayError{} } if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) { log.Log.V(4).Info(fmt.Sprintf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])) return path, newOverlayError(conditionFailure, fmt.Sprintf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])) } return checkConditionsOnArrayOfSameTypes(resource, overlay, path) } func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, path string) (string, overlayError) { for key, overlayValue := range anchors { // skip if key does not have condition anchor if !commonAnchors.IsConditionAnchor(key) { continue } // validate condition anchor map noAnchorKey := removeAnchor(key) curPath := path + noAnchorKey + "/" if resourceValue, ok := resourceMap[noAnchorKey]; ok { // compare entire resourceValue block // return immediately on err since condition fails on this block if newPath, err := compareOverlay(resourceValue, overlayValue, curPath); !reflect.DeepEqual(err, overlayError{}) { return newPath, err } } else { // noAnchorKey doesn't exist in resource return curPath, newOverlayError(conditionNotPresent, fmt.Sprintf("resource field is not present %s", noAnchorKey)) } } return "", overlayError{} } // compareOverlay compare values in anchormap and resourcemap // i.e. check if B1 == B2 // overlay - (A): B1 // resource - A: B2 func compareOverlay(resource, overlay interface{}, path string) (string, overlayError) { if reflect.TypeOf(resource) != reflect.TypeOf(overlay) { log.Log.V(4).Info("element type mismatch", "overlay", overlay, "resource", resource) return path, newOverlayError(conditionFailure, fmt.Sprintf("element type mismatch: overlay %T, resource %T", overlay, resource)) } switch typedOverlay := overlay.(type) { case map[string]interface{}: typedResource := resource.(map[string]interface{}) for key, overlayVal := range typedOverlay { noAnchorKey := removeAnchor(key) curPath := path + noAnchorKey + "/" resourceVal, ok := typedResource[noAnchorKey] if !ok { return curPath, newOverlayError(conditionFailure, fmt.Sprintf("Field %s is not present", noAnchorKey)) } if newPath, err := compareOverlay(resourceVal, overlayVal, curPath); !reflect.DeepEqual(err, overlayError{}) { return newPath, err } } case []interface{}: typedResource := resource.([]interface{}) for _, overlayElement := range typedOverlay { for _, resourceElement := range typedResource { if newPath, err := compareOverlay(resourceElement, overlayElement, path); !reflect.DeepEqual(err, overlayError{}) { return newPath, err } } } case string, float64, int, int64, bool, nil: if !validate.ValidateValueWithPattern(log.Log, resource, overlay) { log.Log.V(4).Info(fmt.Sprintf("Mutate rule: failed validating value %v with overlay %v", resource, overlay)) return path, newOverlayError(conditionFailure, fmt.Sprintf("Failed validating value %v with overlay %v", resource, overlay)) } default: return path, newOverlayError(conditionFailure, fmt.Sprintf("Overlay has unknown type %T, value %v", overlay, overlay)) } return "", overlayError{} } // validateNonAnchorOverlayMap validate anchor condition in overlay block without anchor func validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor map[string]interface{}, path string) (string, overlayError) { // validate resource map (anchors could exist in resource) for key, overlayValue := range overlayWithoutAnchor { curPath := path + key + "/" resourceValue, ok := resourceMap[key] if !ok { if !hasNestedAnchors(overlayValue) { // policy: "(image)": "*:latest", // "imagePullPolicy": "IfNotPresent", // resource: "(image)": "*:latest", // the above case should be allowed continue } } if newPath, err := checkConditions(log.Log, resourceValue, overlayValue, curPath); !reflect.DeepEqual(err, overlayError{}) { return newPath, err } } return "", overlayError{} } func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}, path string) (string, overlayError) { switch overlay[0].(type) { case map[string]interface{}: return checkConditionsOnArrayOfMaps(resource, overlay, path) default: for i, overlayElement := range overlay { curPath := path + strconv.Itoa(i) + "/" path, err := checkConditions(log.Log, resource[i], overlayElement, curPath) if !reflect.DeepEqual(err, overlayError{}) { return path, err } } } return "", overlayError{} } func checkConditionsOnArrayOfMaps(resource, overlay []interface{}, path string) (string, overlayError) { var newPath string var err overlayError for i, overlayElement := range overlay { for _, resourceMap := range resource { curPath := path + strconv.Itoa(i) + "/" newPath, err = checkConditionOnMap(resourceMap.(map[string]interface{}), overlayElement.(map[string]interface{}), curPath) // when resource has multiple same blocks of the overlay block // return true if there is one resource block meet the overlay pattern // reference: TestMeetConditions_AtleastOneExist if reflect.DeepEqual(err, overlayError{}) { return "", overlayError{} } } } // report last error return newPath, err }