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

358 lines
11 KiB
Go

package engine
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
jsonpatch "github.com/evanphx/json-patch"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ProcessOverlay handles validating admission request
// Checks the target resources for rules defined in the policy
func ProcessOverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, result.RuleApplicationResult) {
overlayApplicationResult := result.NewRuleApplicationResult(rule.Name)
if rule.Mutation == nil || rule.Mutation.Overlay == nil {
return nil, overlayApplicationResult
}
var resource interface{}
var appliedPatches []PatchBytes
json.Unmarshal(rawResource, &resource)
patches, res := mutateResourceWithOverlay(resource, *rule.Mutation.Overlay)
overlayApplicationResult.MergeWith(&res)
if overlayApplicationResult.GetReason() == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
return appliedPatches, overlayApplicationResult
}
// OverlayProcessor stores context for overlay mutation process
type OverlayProcessor struct {
appliedPatches []PatchBytes
result result.RuleApplicationResult
}
// mutateResourceWithOverlay is a start of overlaying process
// It assumes that mutation is started from root, so "/" is passed
func mutateResourceWithOverlay(resource, pattern interface{}) ([]PatchBytes, result.RuleApplicationResult) {
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) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
// resource item exists but has different type - replace
// all subtree within this path by overlay
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
patch, res := replaceSubtree(overlay, path)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patch)
}
return appliedPatches, overlayResult
}
return applyOverlayForSameTypes(resource, overlay, path)
}
// applyOverlayForSameTypes is applyOverlay for cases when TypeOf(resource) == TypeOf(overlay)
func applyOverlayForSameTypes(resource, overlay interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
// detect the type of resource and overlay and select corresponding handler
switch typedOverlay := overlay.(type) {
// map
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
patches, res := applyOverlayToMap(typedResource, typedOverlay, path)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patches...)
}
// array
case []interface{}:
typedResource := resource.([]interface{})
patches, res := applyOverlayToArray(typedResource, typedOverlay, path)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patches...)
}
// elementary types
case string, float64, int64, bool:
patch, res := replaceSubtree(overlay, path)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patch)
}
default:
overlayResult.FailWithMessagef("Overlay has unsupported type: %T", overlay)
return nil, overlayResult
}
return appliedPatches, overlayResult
}
// for each overlay and resource map elements applies overlay
func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
for key, value := range overlayMap {
// skip anchor element because it has condition, not
// the value that must replace resource value
if wrappedWithParentheses(key) {
continue
}
currentPath := path + key + "/"
resourcePart, ok := resourceMap[key]
if ok {
// Key exists - go down through the overlay and resource trees
patches, res := applyOverlay(resourcePart, value, currentPath)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patches...)
}
} else {
// Key does not exist - insert entire overlay subtree
patch, res := insertSubtree(value, currentPath)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patch)
}
}
}
return appliedPatches, overlayResult
}
// for each overlay and resource array elements applies overlay
func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
if 0 == len(overlay) {
overlayResult.FailWithMessagef("Empty array detected in the overlay")
return nil, overlayResult
}
if 0 == len(resource) {
patch, res := insertSubtree(overlay, path)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patch)
}
return appliedPatches, res
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
overlayResult.FailWithMessagef("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return nil, overlayResult
}
return applyOverlayToArrayOfSameTypes(resource, overlay, path)
}
func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
switch overlay[0].(type) {
case map[string]interface{}:
return applyOverlayToArrayOfMaps(resource, overlay, path)
default:
lastElementIdx := len(resource)
// Add elements to the end
for i, value := range overlay {
currentPath := path + strconv.Itoa(lastElementIdx+i) + "/"
patch, res := insertSubtree(value, currentPath)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patch)
}
}
}
return appliedPatches, overlayResult
}
func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
lastElementIdx := len(resource)
for i, overlayElement := range overlay {
typedOverlay := overlayElement.(map[string]interface{})
anchors := getAnchorsFromMap(typedOverlay)
if len(anchors) > 0 {
// If we have anchors - choose corresponding resource element and mutate it
patches, res := applyOverlayWithAnchors(resource, overlayElement, anchors, path)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
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) + "/"
patches, res := applyOverlay(resourceElement, overlayElement, currentPath)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patches...)
}
}
} else {
// Overlay subtree has no anchors - insert new element
currentPath := path + strconv.Itoa(lastElementIdx+i) + "/"
patch, res := insertSubtree(overlayElement, currentPath)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patch)
}
}
}
return appliedPatches, overlayResult
}
func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
var appliedPatches []PatchBytes
overlayResult := result.NewRuleApplicationResult("")
for i, resourceElement := range resource {
typedResource := resourceElement.(map[string]interface{})
currentPath := path + strconv.Itoa(i) + "/"
if !skipArrayObject(typedResource, anchors) {
patches, res := applyOverlay(resourceElement, overlay, currentPath)
overlayResult.MergeWith(&res)
if result.Success == overlayResult.GetReason() {
appliedPatches = append(appliedPatches, patches...)
}
}
}
return appliedPatches, overlayResult
}
func insertSubtree(overlay interface{}, path string) (PatchBytes, result.RuleApplicationResult) {
return processSubtree(overlay, path, "add")
}
func replaceSubtree(overlay interface{}, path string) (PatchBytes, result.RuleApplicationResult) {
return processSubtree(overlay, path, "replace")
}
func processSubtree(overlay interface{}, path string, op string) (PatchBytes, result.RuleApplicationResult) {
overlayResult := result.NewRuleApplicationResult("")
if len(path) > 1 && path[len(path)-1] == '/' {
path = path[:len(path)-1]
}
if path == "" {
path = "/"
}
value := prepareJSONValue(overlay)
patchStr := fmt.Sprintf(`{ "op": "%s", "path": "%s", "value": %s }`, op, path, value)
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + patchStr + "]"))
if err != nil {
overlayResult.FailWithMessagef("Failed to make '%s' patch from an overlay '%s' for path %s", op, value, path)
return nil, overlayResult
}
return PatchBytes(patchStr), overlayResult
}
// converts overlay to JSON string to be inserted into the JSON Patch
func prepareJSONValue(overlay interface{}) string {
jsonOverlay, err := json.Marshal(overlay)
if err != nil || hasOnlyAnchors(overlay) {
return ""
}
return string(jsonOverlay)
}
// Anchor has pattern value, so resource shouldn't be mutated with it
// If entire subtree has only anchor keys - we
func hasOnlyAnchors(overlay interface{}) bool {
switch typed := overlay.(type) {
case map[string]interface{}:
if anchors := 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 := getAnchorsFromMap(typed); len(anchors) > 0 {
return true
}
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
return false
case []interface{}:
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
return false
default:
return false
}
}