1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/mutate/overlay.go

487 lines
15 KiB
Go
Raw Normal View History

package mutate
2019-05-21 18:27:56 +03:00
import (
"encoding/json"
"errors"
"fmt"
2019-05-22 18:28:38 +01:00
"reflect"
2019-12-27 12:57:06 -08:00
"regexp"
"strconv"
2019-11-27 17:51:33 -08:00
"strings"
2019-08-23 18:34:23 -07:00
"time"
2019-08-23 18:34:23 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-03-17 16:25:34 -07:00
"sigs.k8s.io/controller-runtime/pkg/log"
jsonpatch "github.com/evanphx/json-patch"
2020-03-17 16:25:34 -07:00
"github.com/go-logr/logr"
2019-10-21 14:22:31 -07:00
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/utils"
2019-05-21 18:27:56 +03:00
)
// ProcessOverlay processes mutation overlay on the resource
2020-03-17 16:25:34 -07:00
func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resource unstructured.Unstructured) (resp response.RuleResponse, patchedResource unstructured.Unstructured) {
2019-08-23 18:34:23 -07:00
startTime := time.Now()
2020-03-17 16:25:34 -07:00
logger := log.WithValues("rule", ruleName)
logger.V(4).Info("started applying overlay rule", "startTime", startTime)
2020-02-14 11:59:28 -08:00
resp.Name = ruleName
resp.Type = utils.Mutation.String()
2019-08-23 18:34:23 -07:00
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
2020-03-17 16:25:34 -07:00
logger.V(4).Info("finished applying overlay rule", "processingTime", resp.RuleStats.ProcessingTime)
2019-08-23 18:34:23 -07:00
}()
2020-03-17 16:25:34 -07:00
patches, overlayerr := processOverlayPatches(logger, resource.UnstructuredContent(), overlay)
if !reflect.DeepEqual(overlayerr, overlayError{}) {
switch overlayerr.statusCode {
case conditionNotPresent:
logger.V(3).Info("skip applying rule", "reason", "conditionNotPresent")
resp.Success = true
return resp, resource
case conditionFailure:
logger.V(3).Info("skip applying rule", "reason", "conditionFailure")
//TODO: send zero response and not consider this as applied?
resp.Success = true
resp.Message = overlayerr.ErrorMsg()
return resp, resource
case overlayFailure:
2020-03-17 16:25:34 -07:00
logger.Info("failed to process overlay")
resp.Success = false
resp.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
return resp, resource
default:
2020-03-17 16:25:34 -07:00
logger.Info("failed to process overlay")
resp.Success = false
resp.Message = fmt.Sprintf("Unknown type of error: %v", overlayerr.Error())
return resp, resource
}
2019-08-23 18:34:23 -07:00
}
logger.V(4).Info("processing overlay rule", "patches", len(patches))
2019-11-13 17:56:56 -08:00
if len(patches) == 0 {
resp.Success = true
return resp, resource
2019-11-13 17:56:56 -08:00
}
2019-08-23 18:34:23 -07:00
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
resp.Success = false
2020-03-17 16:25:34 -07:00
logger.Error(err, "failed to marshal resource")
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
2019-08-23 18:34:23 -07:00
}
var patchResource []byte
logger.V(5).Info("applying overlay patches", "patches", string(utils.JoinPatches(patches)))
patchResource, err = utils.ApplyPatches(resourceRaw, patches)
if err != nil {
2019-11-13 17:56:56 -08:00
msg := fmt.Sprintf("failed to apply JSON patches: %v", err)
resp.Success = false
resp.Message = msg
return resp, resource
}
2019-11-13 17:56:56 -08:00
logger.V(5).Info("patched resource", "patches", string(patchResource))
2019-08-23 18:34:23 -07:00
err = patchedResource.UnmarshalJSON(patchResource)
if err != nil {
2020-03-17 16:25:34 -07:00
logger.Error(err, "failed to unmarshal resource")
resp.Success = false
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
2019-08-23 18:34:23 -07:00
}
// rule application successfully
resp.Success = true
resp.Message = fmt.Sprintf("successfully processed overlay")
resp.Patches = patches
2019-08-23 18:34:23 -07:00
// apply the patches to the resource
return resp, patchedResource
2019-08-23 18:34:23 -07:00
}
2020-03-17 16:25:34 -07:00
func processOverlayPatches(log logr.Logger, resource, overlay interface{}) ([][]byte, overlayError) {
if path, overlayerr := meetConditions(log, resource, overlay); !reflect.DeepEqual(overlayerr, overlayError{}) {
switch overlayerr.statusCode {
// anchor key does not exist in the resource, skip applying policy
case conditionNotPresent:
2020-03-17 16:25:34 -07:00
log.V(4).Info("skip applying policy", "path", path, "error", overlayerr)
return nil, newOverlayError(overlayerr.statusCode, fmt.Sprintf("Policy not applied, condition tag not present: %v at %s", overlayerr.ErrorMsg(), path))
// anchor key is not satisfied in the resource, skip applying policy
case conditionFailure:
// anchor key is not satisfied in the resource, skip applying policy
2020-03-17 16:25:34 -07:00
log.V(4).Info("failed to validate condition", "path", path, "error", overlayerr)
return nil, newOverlayError(overlayerr.statusCode, fmt.Sprintf("Policy not applied, conditions are not met at %s, %v", path, overlayerr))
}
}
2020-03-06 01:09:38 +05:30
patchBytes, err := MutateResourceWithOverlay(resource, overlay)
if err != nil {
return patchBytes, newOverlayError(overlayFailure, err.Error())
2019-05-21 18:27:56 +03:00
}
return patchBytes, overlayError{}
2019-05-21 18:27:56 +03:00
}
2020-03-06 01:09:38 +05:30
// MutateResourceWithOverlay is a start of overlaying process
func MutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) {
2019-06-12 12:21:52 +03:00
// It assumes that mutation is started from root, so "/" is passed
return applyOverlay(resource, pattern, "/")
}
// applyOverlay detects type of current item and goes down through overlay and resource trees applying overlay
func applyOverlay(resource, overlay interface{}, path string) ([][]byte, error) {
2020-01-27 08:58:53 -08:00
2019-05-22 18:28:38 +01:00
// resource item exists but has different type - replace
// all subtree within this path by overlay
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
patch, err := replaceSubtree(overlay, path)
if err != nil {
return nil, err
}
2019-05-22 18:28:38 +01:00
2020-01-27 08:58:53 -08:00
return [][]byte{patch}, nil
}
return applyOverlayForSameTypes(resource, overlay, path)
}
// applyOverlayForSameTypes is applyOverlay for cases when TypeOf(resource) == TypeOf(overlay)
func applyOverlayForSameTypes(resource, overlay interface{}, path string) ([][]byte, error) {
var appliedPatches [][]byte
// detect the type of resource and overlay and select corresponding handler
2019-05-21 18:27:56 +03:00
switch typedOverlay := overlay.(type) {
// map
2019-05-21 18:27:56 +03:00
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
patches, err := applyOverlayToMap(typedResource, typedOverlay, path)
if err != nil {
return nil, err
2019-05-21 18:27:56 +03:00
}
appliedPatches = append(appliedPatches, patches...)
// array
2019-05-21 18:27:56 +03:00
case []interface{}:
typedResource := resource.([]interface{})
patches, err := applyOverlayToArray(typedResource, typedOverlay, path)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patches...)
// elementary types
2019-05-27 18:21:23 +03:00
case string, float64, int64, bool:
patch, err := replaceSubtree(overlay, path)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patch)
default:
return nil, fmt.Errorf("Overlay has unsupported type: %T", overlay)
2019-05-21 18:27:56 +03:00
}
return appliedPatches, nil
2019-05-21 18:27:56 +03:00
}
// for each overlay and resource map elements applies overlay
func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path string) ([][]byte, error) {
var appliedPatches [][]byte
for key, value := range overlayMap {
// skip anchor element because it has condition, not
// the value that must replace resource value
2019-10-21 14:22:31 -07:00
if anchor.IsConditionAnchor(key) {
continue
}
2019-06-14 17:19:32 +03:00
noAnchorKey := removeAnchor(key)
currentPath := path + noAnchorKey + "/"
resourcePart, ok := resourceMap[noAnchorKey]
2019-10-21 14:22:31 -07:00
if ok && !anchor.IsAddingAnchor(key) {
// Key exists - go down through the overlay and resource trees
patches, err := applyOverlay(resourcePart, value, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patches...)
2019-06-14 17:19:32 +03:00
}
if !ok {
// Key does not exist - insert entire overlay subtree
patch, err := insertSubtree(value, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patch)
}
}
return appliedPatches, nil
}
// for each overlay and resource array elements applies overlay
func applyOverlayToArray(resource, overlay []interface{}, path string) ([][]byte, error) {
var appliedPatches [][]byte
if 0 == len(overlay) {
return nil, errors.New("Empty array detected in the overlay")
}
if 0 == len(resource) {
2019-06-12 12:21:52 +03:00
// If array resource is empty, insert part from overlay
patch, err := insertSubtree(overlay, path)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patch)
return appliedPatches, nil
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
return nil, fmt.Errorf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
}
return applyOverlayToArrayOfSameTypes(resource, overlay, path)
}
2019-06-12 12:21:52 +03:00
// applyOverlayToArrayOfSameTypes applies overlay to array elements if they (resource and overlay elements) have same type
func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string) ([][]byte, error) {
var appliedPatches [][]byte
2019-05-21 18:27:56 +03:00
switch overlay[0].(type) {
case map[string]interface{}:
return applyOverlayToArrayOfMaps(resource, overlay, path)
2019-05-21 18:27:56 +03:00
default:
lastElementIdx := len(resource)
// Add elements to the end
for i, value := range overlay {
currentPath := path + strconv.Itoa(lastElementIdx+i) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
patch, err := insertSubtree(value, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patch)
2019-05-21 18:27:56 +03:00
}
}
return appliedPatches, nil
}
// Array of maps needs special handling as far as it can have anchors.
func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([][]byte, error) {
var appliedPatches [][]byte
lastElementIdx := len(resource)
for i, overlayElement := range overlay {
typedOverlay := overlayElement.(map[string]interface{})
anchors := utils.GetAnchorsFromMap(typedOverlay)
if len(anchors) > 0 {
// If we have anchors - choose corresponding resource element and mutate it
patches, err := applyOverlayWithAnchors(resource, overlayElement, path)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patches...)
} else if hasNestedAnchors(overlayElement) {
// If we have anchors on the lower level - continue traversing overlay and resource trees
for j, resourceElement := range resource {
currentPath := path + strconv.Itoa(j) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
patches, err := applyOverlay(resourceElement, overlayElement, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patches...)
}
} else {
// Overlay subtree has no anchors - insert new element
currentPath := path + strconv.Itoa(lastElementIdx+i) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
patch, err := insertSubtree(overlayElement, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patch)
2019-05-22 18:28:38 +01:00
}
2019-05-21 18:27:56 +03:00
}
return appliedPatches, nil
}
func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, path string) ([][]byte, error) {
var appliedPatches [][]byte
for i, resourceElement := range resource {
currentPath := path + strconv.Itoa(i) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
patches, err := applyOverlay(resourceElement, overlay, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patches...)
}
return appliedPatches, nil
2019-05-21 18:27:56 +03:00
}
func insertSubtree(overlay interface{}, path string) ([]byte, error) {
return processSubtree(overlay, path, "add")
}
func replaceSubtree(overlay interface{}, path string) ([]byte, error) {
return processSubtree(overlay, path, "replace")
}
func processSubtree(overlay interface{}, path string, op string) ([]byte, error) {
if len(path) > 1 && path[len(path)-1] == '/' {
path = path[:len(path)-1]
}
2019-11-27 17:51:33 -08:00
path = preparePath(path)
value := prepareJSONValue(overlay)
patchStr := fmt.Sprintf(`{ "op": "%s", "path": "%s", "value":%s }`, op, path, value)
// explicitly handle boolean type in annotation
// keep the type boolean as it is in any other fields
if strings.Contains(path, "/metadata/annotations") {
2019-12-27 12:57:06 -08:00
patchStr = wrapBoolean(patchStr)
}
2019-05-21 18:27:56 +03:00
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + patchStr + "]"))
if err != nil {
return nil, fmt.Errorf("Failed to make '%s' patch from an overlay '%s' for path %s, err: %v", op, value, path, err)
}
return []byte(patchStr), nil
2019-05-21 18:27:56 +03:00
}
2019-11-27 17:51:33 -08:00
func preparePath(path string) string {
if path == "" {
path = "/"
}
annPath := "/metadata/annotations/"
// escape slash in annotation patch
if strings.Contains(path, annPath) {
idx := strings.Index(path, annPath)
p := path[idx+len(annPath):]
path = path[:idx+len(annPath)] + strings.ReplaceAll(p, "/", "~1")
2019-11-27 17:51:33 -08:00
}
return path
}
// converts overlay to JSON string to be inserted into the JSON Patch
func prepareJSONValue(overlay interface{}) string {
2019-07-25 13:14:55 -04:00
var err error
// Need to remove anchors from the overlay struct
overlayWithoutAnchors := removeAnchorFromSubTree(overlay)
jsonOverlay, err := json.Marshal(overlayWithoutAnchors)
if err != nil || hasOnlyAnchors(overlay) {
2020-03-17 16:25:34 -07:00
log.Log.Error(err, "failed to marshall withoutanchors or has only anchors")
return ""
}
return string(jsonOverlay)
}
2019-07-25 13:14:55 -04:00
func removeAnchorFromSubTree(overlay interface{}) interface{} {
var result interface{}
switch typed := overlay.(type) {
case map[string]interface{}:
// assuming only keys have anchors
result = removeAnchroFromMap(typed)
case []interface{}:
arrayResult := make([]interface{}, 0)
for _, value := range typed {
arrayResult = append(arrayResult, removeAnchorFromSubTree(value))
}
result = arrayResult
default:
result = overlay
}
return result
}
func removeAnchroFromMap(overlay map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
2019-07-25 13:14:55 -04:00
for k, v := range overlay {
result[getRawKeyIfWrappedWithAttributes(k)] = removeAnchorFromSubTree(v)
}
return result
}
// Anchor has pattern value, so resource shouldn't be mutated with it
2019-06-12 12:21:52 +03:00
// If entire subtree has only anchor keys - we should skip inserting it
func hasOnlyAnchors(overlay interface{}) bool {
switch typed := overlay.(type) {
case map[string]interface{}:
if anchors := utils.GetAnchorsFromMap(typed); len(anchors) == len(typed) {
return true
}
for _, value := range typed {
if !hasOnlyAnchors(value) {
return false
}
}
case []interface{}:
for _, value := range typed {
if !hasOnlyAnchors(value) {
return false
}
}
default:
return false
}
return true
}
// Checks if subtree has anchors
func hasNestedAnchors(overlay interface{}) bool {
switch typed := overlay.(type) {
case map[string]interface{}:
if anchors := utils.GetAnchorsFromMap(typed); len(anchors) > 0 {
return true
}
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
2019-05-23 14:51:41 +03:00
return false
case []interface{}:
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
return false
default:
return false
}
2019-05-21 18:27:56 +03:00
}
2019-12-27 12:57:06 -08:00
func wrapBoolean(patchStr string) string {
reTrue := regexp.MustCompile(`:\s*true\s*`)
if idx := reTrue.FindStringIndex(patchStr); len(idx) != 0 {
return fmt.Sprintf("%s:\"true\"%s", patchStr[:idx[0]], patchStr[idx[1]:])
}
2019-12-27 12:57:06 -08:00
reFalse := regexp.MustCompile(`:\s*false\s*`)
if idx := reFalse.FindStringIndex(patchStr); len(idx) != 0 {
return fmt.Sprintf("%s:\"false\"%s", patchStr[:idx[0]], patchStr[idx[1]:])
}
return patchStr
}