2019-05-21 18:27:56 +03:00
package engine
import (
"encoding/json"
2019-06-25 18:16:02 -07:00
"errors"
2019-05-22 22:34:25 +01:00
"fmt"
2019-05-22 18:28:38 +01:00
"reflect"
2019-05-23 12:42:58 +03:00
"strconv"
2019-07-26 17:52:12 -07:00
"strings"
2019-08-23 18:34:23 -07:00
"time"
2019-05-22 22:34:25 +01:00
2019-06-25 18:16:02 -07:00
"github.com/golang/glog"
2019-08-23 18:34:23 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-06-25 18:16:02 -07:00
2019-05-22 22:34:25 +01:00
jsonpatch "github.com/evanphx/json-patch"
2019-08-09 16:55:43 -07:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
2019-05-21 18:27:56 +03:00
)
2019-09-03 17:43:36 -07:00
// // rawResource handles validating admission request
// // Checks the target resources for rules defined in the policy
// // TODO: pass in the unstructured object in stead of raw byte?
// func processOverlay(rule kyverno.Rule, rawResource []byte) ([][]byte, error) {
// var resource interface{}
// if err := json.Unmarshal(rawResource, &resource); err != nil {
// glog.V(4).Infof("unable to unmarshal resource : %v", err)
// return nil, err
// }
2019-06-11 16:54:19 +03:00
2019-09-03 17:43:36 -07:00
// resourceInfo := ParseResourceInfoFromObject(rawResource)
// patches, err := processOverlayPatches(resource, rule.Mutation.Overlay)
// if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
// // glog.V(4).Infof("overlay pattern %s does not match resource %s/%s", rule.Mutation.Overlay, resourceUnstr.GetNamespace(), resourceUnstr.GetName())
// glog.Infof("Resource does not meet conditions in overlay pattern, resource=%s, rule=%s\n", resourceInfo, rule.Name)
// // patches, err := processOverlayPatches(resource, rule.Mutation.Overlay)
// // if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
// // glog.V(4).Infof("overlay pattern %s does not match resource %s/%s", rule.Mutation.Overlay, resourceUnstr.GetNamespace(), resourceUnstr.GetName())
// // return nil, nil
// }
2019-07-26 17:52:12 -07:00
2019-09-03 17:43:36 -07:00
// return patches, err
// }
2019-07-26 17:52:12 -07:00
2019-08-23 18:34:23 -07:00
// rawResource handles validating admission request
// Checks the target resources for rules defined in the policy
// TODO: pass in the unstructured object in stead of raw byte?
func processOverlayNew ( rule kyverno . Rule , resource unstructured . Unstructured ) ( response RuleResponse , patchedResource unstructured . Unstructured ) {
startTime := time . Now ( )
glog . V ( 4 ) . Infof ( "started applying overlay rule %q (%v)" , rule . Name , startTime )
response . Name = rule . Name
response . Type = Mutation . String ( )
defer func ( ) {
response . RuleStats . ProcessingTime = time . Since ( startTime )
glog . V ( 4 ) . Infof ( "finished applying overlay rule %q (%v)" , response . Name , response . RuleStats . ProcessingTime )
} ( )
patches , err := processOverlayPatches ( resource . UnstructuredContent ( ) , rule . Mutation . Overlay )
// resource does not satisfy the overlay pattern, we dont apply this rule
if err != nil && strings . Contains ( err . Error ( ) , "Conditions are not met" ) {
glog . V ( 4 ) . Infof ( "Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s" , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , rule . Name , rule . Mutation . Overlay )
//TODO: send zero response and not consider this as applied?
return RuleResponse { } , resource
}
if err != nil {
// rule application failed
response . Success = false
response . Message = fmt . Sprintf ( "failed to process overlay: %v" , err )
return response , resource
}
// convert to RAW
resourceRaw , err := resource . MarshalJSON ( )
if err != nil {
response . Success = false
glog . Infof ( "unable to marshall resource: %v" , err )
response . Message = fmt . Sprintf ( "failed to process JSON patches: %v" , err )
return response , resource
}
var patchResource [ ] byte
2019-08-29 18:48:58 -07:00
patchResource , err = ApplyPatches ( resourceRaw , patches )
if err != nil {
glog . Info ( "failed to apply patch" )
response . Success = false
response . Message = fmt . Sprintf ( "failed to apply JSON patches: %v" , err )
return response , resource
}
2019-08-23 18:34:23 -07:00
err = patchedResource . UnmarshalJSON ( patchResource )
if err != nil {
glog . Infof ( "failed to unmarshall resource to undstructured: %v" , err )
response . Success = false
response . Message = fmt . Sprintf ( "failed to process JSON patches: %v" , err )
return response , resource
}
// rule application succesfuly
response . Success = true
response . Message = fmt . Sprintf ( "succesfully process overlay" )
response . Patches = patches
// apply the patches to the resource
return response , patchedResource
}
2019-07-26 17:52:12 -07:00
func processOverlayPatches ( resource , overlay interface { } ) ( [ ] [ ] byte , error ) {
if ! meetConditions ( resource , overlay ) {
return nil , errors . New ( "Conditions are not met" )
2019-05-21 18:27:56 +03:00
}
2019-07-26 17:52:12 -07:00
return mutateResourceWithOverlay ( resource , overlay )
2019-05-21 18:27:56 +03:00
}
2019-06-11 16:54:19 +03:00
// mutateResourceWithOverlay is a start of overlaying process
2019-07-05 15:20:43 -07:00
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
2019-06-11 16:54:19 +03:00
return applyOverlay ( resource , pattern , "/" )
}
// applyOverlay detects type of current item and goes down through overlay and resource trees applying overlay
2019-07-05 15:20:43 -07:00
func applyOverlay ( resource , overlay interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
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 ) {
2019-06-25 18:16:02 -07:00
patch , err := replaceSubtree ( overlay , path )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-05-22 18:28:38 +01:00
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-08-23 18:34:23 -07:00
//TODO : check if return is needed ?
2019-06-25 18:16:02 -07:00
}
2019-06-11 16:54:19 +03:00
return applyOverlayForSameTypes ( resource , overlay , path )
}
// applyOverlayForSameTypes is applyOverlay for cases when TypeOf(resource) == TypeOf(overlay)
2019-07-05 15:20:43 -07:00
func applyOverlayForSameTypes ( resource , overlay interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
// detect the type of resource and overlay and select corresponding handler
2019-05-21 18:27:56 +03:00
switch typedOverlay := overlay . ( type ) {
2019-06-11 16:54:19 +03:00
// map
2019-05-21 18:27:56 +03:00
case map [ string ] interface { } :
typedResource := resource . ( map [ string ] interface { } )
2019-06-25 18:16:02 -07:00
patches , err := applyOverlayToMap ( typedResource , typedOverlay , path )
if err != nil {
return nil , err
2019-05-21 18:27:56 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
// array
2019-05-21 18:27:56 +03:00
case [ ] interface { } :
typedResource := resource . ( [ ] interface { } )
2019-06-25 18:16:02 -07:00
patches , err := applyOverlayToArray ( typedResource , typedOverlay , path )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
// elementary types
2019-05-27 18:21:23 +03:00
case string , float64 , int64 , bool :
2019-06-25 18:16:02 -07:00
patch , err := replaceSubtree ( overlay , path )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-05-23 17:31:34 +03:00
default :
2019-06-25 18:16:02 -07:00
return nil , fmt . Errorf ( "Overlay has unsupported type: %T" , overlay )
2019-05-21 18:27:56 +03:00
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-21 18:27:56 +03:00
}
2019-06-11 16:54:19 +03:00
// for each overlay and resource map elements applies overlay
2019-07-05 15:20:43 -07:00
func applyOverlayToMap ( resourceMap , overlayMap map [ string ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
for key , value := range overlayMap {
// skip anchor element because it has condition, not
// the value that must replace resource value
2019-06-13 17:20:00 +03:00
if isConditionAnchor ( key ) {
2019-06-11 16:54:19 +03:00
continue
}
2019-06-14 17:19:32 +03:00
noAnchorKey := removeAnchor ( key )
currentPath := path + noAnchorKey + "/"
resourcePart , ok := resourceMap [ noAnchorKey ]
2019-06-11 16:54:19 +03:00
2019-06-14 17:19:32 +03:00
if ok && ! isAddingAnchor ( key ) {
2019-06-11 16:54:19 +03:00
// Key exists - go down through the overlay and resource trees
2019-06-25 18:16:02 -07:00
patches , err := applyOverlay ( resourcePart , value , currentPath )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-14 17:19:32 +03:00
}
if ! ok {
2019-06-11 16:54:19 +03:00
// Key does not exist - insert entire overlay subtree
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( value , currentPath )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-06-11 16:54:19 +03:00
}
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-06-11 16:54:19 +03:00
}
// for each overlay and resource array elements applies overlay
2019-07-05 15:20:43 -07:00
func applyOverlayToArray ( resource , overlay [ ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
if 0 == len ( overlay ) {
2019-06-25 18:16:02 -07:00
return nil , errors . New ( "Empty array detected in the overlay" )
2019-06-11 16:54:19 +03:00
}
if 0 == len ( resource ) {
2019-06-12 12:21:52 +03:00
// If array resource is empty, insert part from overlay
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( overlay , path )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-06-11 16:54:19 +03:00
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-22 22:34:25 +01:00
}
if reflect . TypeOf ( resource [ 0 ] ) != reflect . TypeOf ( overlay [ 0 ] ) {
2019-06-25 18:16:02 -07:00
return nil , fmt . Errorf ( "Overlay array and resource array have elements of different types: %T and %T" , overlay [ 0 ] , resource [ 0 ] )
2019-05-22 22:34:25 +01:00
}
2019-06-11 16:54:19 +03:00
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
2019-07-05 15:20:43 -07:00
func applyOverlayToArrayOfSameTypes ( resource , overlay [ ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
2019-05-21 18:27:56 +03:00
switch overlay [ 0 ] . ( type ) {
case map [ string ] interface { } :
2019-06-11 16:54:19 +03:00
return applyOverlayToArrayOfMaps ( resource , overlay , path )
2019-05-21 18:27:56 +03:00
default :
2019-06-11 16:54:19 +03:00
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/
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( value , currentPath )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-05-21 18:27:56 +03:00
}
}
2019-05-22 22:34:25 +01:00
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-22 22:34:25 +01:00
}
2019-06-12 13:12:36 +03:00
// Array of maps needs special handling as far as it can have anchors.
2019-07-05 15:20:43 -07:00
func applyOverlayToArrayOfMaps ( resource , overlay [ ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-05-22 22:34:25 +01:00
2019-06-11 16:54:19 +03:00
lastElementIdx := len ( resource )
for i , overlayElement := range overlay {
typedOverlay := overlayElement . ( map [ string ] interface { } )
anchors := getAnchorsFromMap ( typedOverlay )
2019-05-22 22:34:25 +01:00
2019-06-11 16:54:19 +03:00
if len ( anchors ) > 0 {
// If we have anchors - choose corresponding resource element and mutate it
2019-06-25 18:16:02 -07:00
patches , err := applyOverlayWithAnchors ( resource , overlayElement , anchors , path )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
} 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/
2019-06-25 18:16:02 -07:00
patches , err := applyOverlay ( resourceElement , overlayElement , currentPath )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-05-22 22:34:25 +01:00
}
2019-06-11 16:54:19 +03:00
} 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/
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( overlayElement , currentPath )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-05-22 18:28:38 +01:00
}
2019-05-21 18:27:56 +03:00
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-06-11 16:54:19 +03:00
}
2019-07-05 15:20:43 -07:00
func applyOverlayWithAnchors ( resource [ ] interface { } , overlay interface { } , anchors map [ string ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
for i , resourceElement := range resource {
typedResource := resourceElement . ( map [ string ] interface { } )
currentPath := path + strconv . Itoa ( i ) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
2019-06-11 16:54:19 +03:00
if ! skipArrayObject ( typedResource , anchors ) {
2019-06-25 18:16:02 -07:00
patches , err := applyOverlay ( resourceElement , overlay , currentPath )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
}
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-21 18:27:56 +03:00
}
2019-07-05 15:20:43 -07:00
func insertSubtree ( overlay interface { } , path string ) ( [ ] byte , error ) {
2019-06-11 16:54:19 +03:00
return processSubtree ( overlay , path , "add" )
2019-05-22 22:34:25 +01:00
}
2019-07-05 15:20:43 -07:00
func replaceSubtree ( overlay interface { } , path string ) ( [ ] byte , error ) {
2019-06-11 16:54:19 +03:00
return processSubtree ( overlay , path , "replace" )
2019-05-22 22:34:25 +01:00
}
2019-07-05 15:20:43 -07:00
func processSubtree ( overlay interface { } , path string , op string ) ( [ ] byte , error ) {
2019-06-11 16:54:19 +03:00
2019-05-22 22:34:25 +01:00
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 )
2019-05-21 18:27:56 +03:00
2019-05-22 22:34:25 +01:00
// check the patch
_ , err := jsonpatch . DecodePatch ( [ ] byte ( "[" + patchStr + "]" ) )
if err != nil {
2019-06-25 18:16:02 -07:00
glog . V ( 3 ) . Info ( err )
return nil , fmt . Errorf ( "Failed to make '%s' patch from an overlay '%s' for path %s" , op , value , path )
2019-05-22 22:34:25 +01:00
}
2019-07-05 15:20:43 -07:00
return [ ] byte ( patchStr ) , nil
2019-05-21 18:27:56 +03:00
}
2019-05-23 17:31:34 +03:00
// converts overlay to JSON string to be inserted into the JSON Patch
2019-05-22 22:34:25 +01:00
func prepareJSONValue ( overlay interface { } ) string {
2019-07-25 13:14:55 -04:00
var err error
2019-06-11 16:54:19 +03:00
if err != nil || hasOnlyAnchors ( overlay ) {
2019-06-25 18:16:02 -07:00
glog . V ( 3 ) . Info ( err )
2019-05-22 22:34:25 +01:00
return ""
}
2019-07-25 13:14:55 -04:00
// Need to remove anchors from the overlay struct
overlayWithoutAnchors := removeAnchorFromSubTree ( overlay )
jsonOverlay , err := json . Marshal ( overlayWithoutAnchors )
2019-06-11 16:54:19 +03:00
return string ( jsonOverlay )
2019-05-22 22:34:25 +01:00
}
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 { } , 0 )
for k , v := range overlay {
result [ getRawKeyIfWrappedWithAttributes ( k ) ] = removeAnchorFromSubTree ( v )
}
return result
}
2019-06-11 16:54:19 +03:00
// 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
2019-05-22 22:34:25 +01:00
func hasOnlyAnchors ( overlay interface { } ) bool {
switch typed := overlay . ( type ) {
case map [ string ] interface { } :
2019-06-05 17:43:59 -07:00
if anchors := getAnchorsFromMap ( typed ) ; len ( anchors ) == len ( typed ) {
2019-05-22 22:34:25 +01:00
return true
}
for _ , value := range typed {
if ! hasOnlyAnchors ( value ) {
return false
}
}
2019-06-11 16:54:19 +03:00
case [ ] interface { } :
for _ , value := range typed {
if ! hasOnlyAnchors ( value ) {
return false
}
}
2019-05-22 22:34:25 +01:00
default :
return false
}
2019-06-11 16:54:19 +03:00
return true
2019-05-22 22:34:25 +01:00
}
2019-06-11 16:54:19 +03:00
// Checks if subtree has anchors
2019-05-22 22:34:25 +01:00
func hasNestedAnchors ( overlay interface { } ) bool {
switch typed := overlay . ( type ) {
case map [ string ] interface { } :
2019-06-05 17:43:59 -07:00
if anchors := getAnchorsFromMap ( typed ) ; len ( anchors ) > 0 {
2019-05-22 22:34:25 +01:00
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
}
}
2019-05-22 22:34:25 +01:00
return false
default :
return false
}
2019-05-21 18:27:56 +03:00
}