From b7655ae7478a9939bcfadaf72cbb59a341f8887a Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Tue, 25 Jun 2019 18:16:02 -0700 Subject: [PATCH] introduce info struct for engine responses --- pkg/engine/anchor.go | 47 +-- pkg/engine/generation.go | 28 +- pkg/engine/mutation.go | 53 ++-- pkg/engine/overlay.go | 197 ++++++------ pkg/engine/patches.go | 32 +- pkg/engine/validation.go | 152 +++++----- pkg/info/info.go | 96 ++++++ pkg/webhooks/server.go | 186 +++++++++--- vendor/github.com/evanphx/json-patch/patch.go | 286 +++++++++++------- 9 files changed, 659 insertions(+), 418 deletions(-) create mode 100644 pkg/info/info.go diff --git a/pkg/engine/anchor.go b/pkg/engine/anchor.go index a511f9a659..cdce103045 100644 --- a/pkg/engine/anchor.go +++ b/pkg/engine/anchor.go @@ -1,9 +1,8 @@ package engine import ( + "fmt" "strconv" - - "github.com/nirmata/kyverno/pkg/result" ) // CreateAnchorHandler is a factory that create anchor handlers @@ -23,7 +22,7 @@ func CreateAnchorHandler(anchor string, pattern interface{}, path string) Valida // resourcePart must be an array of dictionaries // patternPart must be a dictionary with anchors type ValidationAnchorHandler interface { - Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult + Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error } // NoAnchorValidationHandler just calls validateMap @@ -41,23 +40,23 @@ func NewNoAnchorValidationHandler(path string) ValidationAnchorHandler { } // Handle performs validation in context of NoAnchorValidationHandler -func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult { - handlingResult := result.NewRuleApplicationResult("") +func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error { for i, resourceElement := range resourcePart { currentPath := navh.path + strconv.Itoa(i) + "/" typedResourceElement, ok := resourceElement.(map[string]interface{}) if !ok { - handlingResult.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement) - return handlingResult + return fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement) } - res := validateMap(typedResourceElement, patternPart, originPattern, currentPath) - handlingResult.MergeWith(&res) + err := validateMap(typedResourceElement, patternPart, originPattern, currentPath) + if err != nil { + return err + } } - return handlingResult + return nil } // ConditionAnchorValidationHandler performs @@ -81,7 +80,7 @@ func NewConditionAnchorValidationHandler(anchor string, pattern interface{}, pat } // Handle performs validation in context of ConditionAnchorValidationHandler -func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult { +func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error { _, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path, originPattern) return handlingResult @@ -110,14 +109,16 @@ func NewExistanceAnchorValidationHandler(anchor string, pattern interface{}, pat } // Handle performs validation in context of ExistanceAnchorValidationHandler -func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult { - anchoredEntries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern) - +func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error { + anchoredEntries, err := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern) + if err != nil { + return err + } if 0 == anchoredEntries { - handlingResult.FailWithMessagef("Existance anchor %s used, but no suitable entries were found", eavh.anchor) + return fmt.Errorf("Existance anchor %s used, but no suitable entries were found", eavh.anchor) } - return handlingResult + return nil } // check if array element fits the anchor @@ -134,8 +135,7 @@ func checkForAnchorCondition(anchor string, pattern interface{}, resourceMap map // both () and ^() are checking conditions and have a lot of similar logic // the only difference is that ^() requires existace of one element // anchoredEntries var counts this occurences. -func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, result.RuleApplicationResult) { - handlingResult := result.NewRuleApplicationResult("") +func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, error) { anchoredEntries := 0 for i, resourceElement := range resourcePart { @@ -143,8 +143,7 @@ func handleConditionCases(resourcePart []interface{}, patternPart map[string]int typedResourceElement, ok := resourceElement.(map[string]interface{}) if !ok { - handlingResult.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement) - break + return 0, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement) } if !checkForAnchorCondition(anchor, pattern, typedResourceElement) { @@ -152,9 +151,11 @@ func handleConditionCases(resourcePart []interface{}, patternPart map[string]int } anchoredEntries++ - res := validateMap(typedResourceElement, patternPart, originPattern, currentPath) - handlingResult.MergeWith(&res) + err := validateMap(typedResourceElement, patternPart, originPattern, currentPath) + if err != nil { + return 0, err + } } - return anchoredEntries, handlingResult + return anchoredEntries, nil } diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index b4e45a672e..da968288ae 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -6,20 +6,22 @@ import ( "github.com/golang/glog" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" client "github.com/nirmata/kyverno/pkg/dclient" + "github.com/nirmata/kyverno/pkg/info" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Generate should be called to process generate rules on the resource -func Generate(client *client.Client, policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) { - // configMapGenerator and secretGenerator can be applied only to namespaces - // TODO: support for any resource - if gvk.Kind != "Namespace" { - return - } +func Generate(client *client.Client, policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []*info.RuleInfo { + ris := []*info.RuleInfo{} for _, rule := range policy.Spec.Rules { - ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk) + if rule.Generation == nil { + continue + } + ri := info.NewRuleInfo(rule.Name) + + ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk) if !ok { glog.Infof("Rule is not applicable to the request: rule name = %s in policy %s \n", rule.Name, policy.ObjectMeta.Name) continue @@ -27,17 +29,17 @@ func Generate(client *client.Client, policy kubepolicy.Policy, rawResource []byt err := applyRuleGenerator(client, rawResource, rule.Generation, gvk) if err != nil { - glog.Warningf("Failed to apply rule generator: %v", err) + ri.Fail() + ri.Addf(" Failed to apply rule generator. err %v", err) + } else { + ri.Add("Generation succesfully") } + ris = append(ris, ri) } + return ris } -// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule -// TODO: plan to support all kinds of generator func applyRuleGenerator(client *client.Client, rawResource []byte, generator *kubepolicy.Generation, gvk metav1.GroupVersionKind) error { - if generator == nil { - return nil - } var err error diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index fa5ceebca6..e5676dec36 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -1,51 +1,60 @@ package engine import ( + "github.com/golang/glog" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kyverno/pkg/result" + "github.com/nirmata/kyverno/pkg/info" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Mutate performs mutation. Overlay first and then mutation patches -func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, result.Result) { +func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, []*info.RuleInfo) { var allPatches []PatchBytes - patchedDocument := rawResource - policyResult := result.NewPolicyApplicationResult(policy.Name) + ris := []*info.RuleInfo{} for _, rule := range policy.Spec.Rules { if rule.Mutation == nil { continue } - - ruleApplicationResult := result.NewRuleApplicationResult(rule.Name) + ri := info.NewRuleInfo(rule.Name) ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk) if !ok { - ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name) - } else { - // Process Overlay - overlayPatches, ruleResult := ProcessOverlay(rule, rawResource, gvk) - if result.Success != ruleResult.GetReason() { - ruleApplicationResult.MergeWith(&ruleResult) - ruleApplicationResult.AddMessagef("Overlay application has failed for rule %s in policy %s\n", rule.Name, policy.ObjectMeta.Name) + glog.V(3).Info("Not applicable on specified resource kind%s", gvk.Kind) + continue + } + // Process Overlay + if rule.Mutation.Overlay != nil { + overlayPatches, err := ProcessOverlay(rule, rawResource, gvk) + if err != nil { + ri.Fail() + ri.Addf("Overlay application has failed. err %s", err) } else { - ruleApplicationResult.AddMessagef("Success") + ri.Add("Overlay succesfully applied") + //TODO: patchbytes -> string + //glog.V(3).Info(" Overlay succesfully applied. Patch %s", string(overlayPatches)) allPatches = append(allPatches, overlayPatches...) } + } - // Process Patches - rulePatches, ruleResult := ProcessPatches(rule, patchedDocument) - if result.Success != ruleResult.GetReason() { - ruleApplicationResult.MergeWith(&ruleResult) - ruleApplicationResult.AddMessagef("Patches application has failed for rule %s in policy %s\n", rule.Name, policy.ObjectMeta.Name) + // Process Patches + if len(rule.Mutation.Patches) != 0 { + rulePatches, errs := ProcessPatches(rule, patchedDocument) + if len(errs) > 0 { + ri.Fail() + for _, err := range errs { + ri.Addf("Patches application has failed. err %s", err) + } } else { - ruleApplicationResult.AddMessagef("Success") + ri.Add("Patches succesfully applied") + //TODO: patchbytes -> string + //glog.V(3).Info("Patches succesfully applied. Patch %s", string(overlayPatches)) allPatches = append(allPatches, rulePatches...) } } - policyResult = result.Append(policyResult, &ruleApplicationResult) + ris = append(ris, ri) } - return allPatches, policyResult + return allPatches, ris } diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index 384ff02e39..73f77c8a1e 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -2,109 +2,99 @@ package engine import ( "encoding/json" + "errors" "fmt" "reflect" "strconv" + "github.com/golang/glog" + 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 - } +func ProcessOverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, error) { 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...) + err := json.Unmarshal(rawResource, &resource) + if err != nil { + return nil, err } - return appliedPatches, overlayApplicationResult + patches, err := mutateResourceWithOverlay(resource, *rule.Mutation.Overlay) + if err != nil { + return nil, err + } + appliedPatches = append(appliedPatches, patches...) + + return appliedPatches, err } // mutateResourceWithOverlay is a start of overlaying process -func mutateResourceWithOverlay(resource, pattern interface{}) ([]PatchBytes, result.RuleApplicationResult) { +func mutateResourceWithOverlay(resource, pattern interface{}) ([]PatchBytes, error) { // 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) ([]PatchBytes, result.RuleApplicationResult) { +func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, error) { 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) + patch, err := replaceSubtree(overlay, path) + if err != nil { + return nil, err } - return appliedPatches, overlayResult - } + appliedPatches = append(appliedPatches, patch) + } 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) { +func applyOverlayForSameTypes(resource, overlay interface{}, path string) ([]PatchBytes, error) { 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...) + patches, err := applyOverlayToMap(typedResource, typedOverlay, path) + if err != nil { + return nil, err } + 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...) + patches, err := applyOverlayToArray(typedResource, typedOverlay, path) + if err != nil { + return nil, err } + 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) + patch, err := replaceSubtree(overlay, path) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patch) default: - overlayResult.FailWithMessagef("Overlay has unsupported type: %T", overlay) - return nil, overlayResult + return nil, fmt.Errorf("Overlay has unsupported type: %T", overlay) } - return appliedPatches, overlayResult + return appliedPatches, nil } // for each overlay and resource map elements applies overlay -func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) { +func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path string) ([]PatchBytes, error) { var appliedPatches []PatchBytes - overlayResult := result.NewRuleApplicationResult("") for key, value := range overlayMap { // skip anchor element because it has condition, not @@ -119,62 +109,55 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri if ok && !isAddingAnchor(key) { // 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...) + patches, err := applyOverlay(resourcePart, value, currentPath) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patches...) } if !ok { // 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) + patch, err := insertSubtree(value, currentPath) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patch) } } - return appliedPatches, overlayResult + return appliedPatches, nil } // for each overlay and resource array elements applies overlay -func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) { +func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchBytes, error) { var appliedPatches []PatchBytes - overlayResult := result.NewRuleApplicationResult("") if 0 == len(overlay) { - overlayResult.FailWithMessagef("Empty array detected in the overlay") - return nil, overlayResult + return nil, errors.New("Empty array detected in the overlay") } if 0 == len(resource) { // If array resource is empty, insert part from overlay - patch, res := insertSubtree(overlay, path) - overlayResult.MergeWith(&res) - - if result.Success == overlayResult.GetReason() { - appliedPatches = append(appliedPatches, patch) + patch, err := insertSubtree(overlay, path) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patch) - return appliedPatches, res + return appliedPatches, nil } 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 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) } // applyOverlayToArrayOfSameTypes applies overlay to array elements if they (resource and overlay elements) have same type -func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) { +func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string) ([]PatchBytes, error) { var appliedPatches []PatchBytes - overlayResult := result.NewRuleApplicationResult("") switch overlay[0].(type) { case map[string]interface{}: @@ -186,22 +169,20 @@ func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string for i, value := range overlay { currentPath := path + strconv.Itoa(lastElementIdx+i) + "/" // currentPath example: /spec/template/spec/containers/3/ - patch, res := insertSubtree(value, currentPath) - overlayResult.MergeWith(&res) - - if result.Success == overlayResult.GetReason() { - appliedPatches = append(appliedPatches, patch) + patch, err := insertSubtree(value, currentPath) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patch) } } - return appliedPatches, overlayResult + return appliedPatches, nil } // Array of maps needs special handling as far as it can have anchors. -func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) { +func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]PatchBytes, error) { var appliedPatches []PatchBytes - overlayResult := result.NewRuleApplicationResult("") lastElementIdx := len(resource) for i, overlayElement := range overlay { @@ -210,43 +191,39 @@ func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([] 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...) + patches, err := applyOverlayWithAnchors(resource, overlayElement, anchors, 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) + "/" // currentPath example: /spec/template/spec/containers/3/ - patches, res := applyOverlay(resourceElement, overlayElement, currentPath) - overlayResult.MergeWith(&res) - - if result.Success == overlayResult.GetReason() { - appliedPatches = append(appliedPatches, patches...) + 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) + "/" // currentPath example: /spec/template/spec/containers/3/ - patch, res := insertSubtree(overlayElement, currentPath) - overlayResult.MergeWith(&res) - - if result.Success == overlayResult.GetReason() { - appliedPatches = append(appliedPatches, patch) + patch, err := insertSubtree(overlayElement, currentPath) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patch) } } - return appliedPatches, overlayResult + return appliedPatches, nil } -func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) { +func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([]PatchBytes, error) { var appliedPatches []PatchBytes - overlayResult := result.NewRuleApplicationResult("") for i, resourceElement := range resource { typedResource := resourceElement.(map[string]interface{}) @@ -254,27 +231,26 @@ func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchor currentPath := path + strconv.Itoa(i) + "/" // currentPath example: /spec/template/spec/containers/3/ if !skipArrayObject(typedResource, anchors) { - patches, res := applyOverlay(resourceElement, overlay, currentPath) - overlayResult.MergeWith(&res) - if result.Success == overlayResult.GetReason() { - appliedPatches = append(appliedPatches, patches...) + patches, err := applyOverlay(resourceElement, overlay, currentPath) + if err != nil { + return nil, err } + appliedPatches = append(appliedPatches, patches...) } } - return appliedPatches, overlayResult + return appliedPatches, nil } -func insertSubtree(overlay interface{}, path string) (PatchBytes, result.RuleApplicationResult) { +func insertSubtree(overlay interface{}, path string) (PatchBytes, error) { return processSubtree(overlay, path, "add") } -func replaceSubtree(overlay interface{}, path string) (PatchBytes, result.RuleApplicationResult) { +func replaceSubtree(overlay interface{}, path string) (PatchBytes, error) { return processSubtree(overlay, path, "replace") } -func processSubtree(overlay interface{}, path string, op string) (PatchBytes, result.RuleApplicationResult) { - overlayResult := result.NewRuleApplicationResult("") +func processSubtree(overlay interface{}, path string, op string) (PatchBytes, error) { if len(path) > 1 && path[len(path)-1] == '/' { path = path[:len(path)-1] @@ -290,11 +266,11 @@ func processSubtree(overlay interface{}, path string, op string) (PatchBytes, re // 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 + glog.V(3).Info(err) + return nil, fmt.Errorf("Failed to make '%s' patch from an overlay '%s' for path %s", op, value, path) } - return PatchBytes(patchStr), overlayResult + return PatchBytes(patchStr), nil } // converts overlay to JSON string to be inserted into the JSON Patch @@ -302,6 +278,7 @@ func prepareJSONValue(overlay interface{}) string { jsonOverlay, err := json.Marshal(overlay) if err != nil || hasOnlyAnchors(overlay) { + glog.V(3).Info(err) return "" } diff --git a/pkg/engine/patches.go b/pkg/engine/patches.go index 20277f362f..31ab31b42e 100644 --- a/pkg/engine/patches.go +++ b/pkg/engine/patches.go @@ -2,11 +2,10 @@ package engine import ( "encoding/json" - "fmt" + "errors" jsonpatch "github.com/evanphx/json-patch" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kyverno/pkg/result" ) // PatchBytes stands for []byte @@ -14,40 +13,27 @@ type PatchBytes []byte // ProcessPatches Returns array from separate patches that can be applied to the document // Returns error ONLY in case when creation of resource should be denied. -func ProcessPatches(rule kubepolicy.Rule, resource []byte) ([]PatchBytes, result.RuleApplicationResult) { - res := result.NewRuleApplicationResult(rule.Name) - if rule.Mutation == nil || len(rule.Mutation.Patches) == 0 { - return nil, res - } - +func ProcessPatches(rule kubepolicy.Rule, resource []byte) (allPatches []PatchBytes, errs []error) { if len(resource) == 0 { - res.AddMessagef("Source document for patching is empty") - res.Reason = result.Failed - return nil, res + errs = append(errs, errors.New("Source document for patching is empty")) + return nil, errs } - - var allPatches []PatchBytes patchedDocument := resource - for i, patch := range rule.Mutation.Patches { + for _, patch := range rule.Mutation.Patches { patchRaw, err := json.Marshal(patch) if err != nil { - + errs = append(errs, err) + continue } patchedDocument, err = applyPatch(patchedDocument, patchRaw) if err != nil { - // TODO: continue on error if one of the patches fails, will add the failure event in such case - if patch.Operation == "remove" { - continue - } - message := fmt.Sprintf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err) - res.Messages = append(res.Messages, message) + errs = append(errs, err) continue } - allPatches = append(allPatches, patchRaw) } - return allPatches, res + return allPatches, errs } // JoinPatches joins array of serialized JSON patches to the single JSONPatch array diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 09f5a487d4..64b110b560 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -2,73 +2,73 @@ package engine import ( "encoding/json" + "errors" "fmt" "path/filepath" "reflect" "strconv" "strings" + "github.com/golang/glog" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kyverno/pkg/result" + "github.com/nirmata/kyverno/pkg/info" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Validate handles validating admission request // Checks the target resources for rules defined in the policy -func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) result.Result { +func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]*info.RuleInfo, error) { var resource interface{} - json.Unmarshal(rawResource, &resource) + ris := []*info.RuleInfo{} - policyResult := result.NewPolicyApplicationResult(policy.Name) + err := json.Unmarshal(rawResource, &resource) + if err != nil { + return nil, err + } for _, rule := range policy.Spec.Rules { if rule.Validation == nil { continue } - - ruleApplicationResult := result.NewRuleApplicationResult(rule.Name) + ri := info.NewRuleInfo(rule.Name) ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk) if !ok { - ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name) - policyResult = result.Append(policyResult, &ruleApplicationResult) + glog.V(3).Info("Not applicable on specified resource kind%s", gvk.Kind) continue } - validationResult := validateResourceWithPattern(resource, rule.Validation.Pattern) - if result.Success != validationResult.Reason { - ruleApplicationResult.AddMessagef(*rule.Validation.Message) - ruleApplicationResult.MergeWith(&validationResult) + err := validateResourceWithPattern(resource, rule.Validation.Pattern) + if err != nil { + ri.Fail() + ri.Addf("Validation has failed. err %s", err) } else { - ruleApplicationResult.AddMessagef("Success") - } + ri.Add("Validation succesfully") - policyResult = result.Append(policyResult, &ruleApplicationResult) + } + ris = append(ris, ri) } - return policyResult + return ris, nil } // validateResourceWithPattern is a start of element-by-element validation process // It assumes that validation is started from root, so "/" is passed -func validateResourceWithPattern(resource, pattern interface{}) result.RuleApplicationResult { +func validateResourceWithPattern(resource, pattern interface{}) error { return validateResourceElement(resource, pattern, pattern, "/") } // validateResourceElement detects the element type (map, array, nil, string, int, bool, float) // and calls corresponding handler // Pattern tree and resource tree can have different structure. In this case validation fails -func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) result.RuleApplicationResult { - res := result.NewRuleApplicationResult("") - // TODO: Move similar message templates to message package - +func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) error { + var err error switch typedPatternElement := patternElement.(type) { // map case map[string]interface{}: typedResourceElement, ok := resourceElement.(map[string]interface{}) if !ok { - res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) - return res + return fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) } return validateMap(typedResourceElement, typedPatternElement, originPattern, path) @@ -76,8 +76,7 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte case []interface{}: typedResourceElement, ok := resourceElement.([]interface{}) if !ok { - res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) - return res + return fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) } return validateArray(typedResourceElement, typedPatternElement, originPattern, path) @@ -86,27 +85,25 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte /*Analyze pattern */ if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String { if isStringIsReference(checkedPattern.String()) { //check for $ anchor - patternElement, res = actualizePattern(originPattern, checkedPattern.String(), path) - if result.Failed == res.Reason { - return res + patternElement, err = actualizePattern(originPattern, checkedPattern.String(), path) + if err != nil { + return err } } } if !ValidateValueWithPattern(resourceElement, patternElement) { - res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path) + return fmt.Errorf("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path) } - return res default: - res.FailWithMessagef("Pattern contains unknown type %T. Path: %s", patternElement, path) - return res + return fmt.Errorf("Pattern contains unknown type %T. Path: %s", patternElement, path) } + return nil } // If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap // For each element of the map we must detect the type again, so we pass these elements to validateResourceElement -func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) result.RuleApplicationResult { - res := result.NewRuleApplicationResult("") +func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) error { for key, patternElement := range patternMap { key = removeAnchor(key) @@ -115,43 +112,47 @@ func validateMap(resourceMap, patternMap map[string]interface{}, origPattern int if patternElement == "*" && resourceMap[key] != nil { continue } else if patternElement == "*" && resourceMap[key] == nil { - res.FailWithMessagef("Field %s is not present", key) + return fmt.Errorf("Field %s is not present", key) } else { - elementResult := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/") - res.MergeWith(&elementResult) + err := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/") + if err != nil { + return err + } } } - return res + return nil } -func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) result.RuleApplicationResult { - res := result.NewRuleApplicationResult("") +func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) error { if 0 == len(patternArray) { - return res + return fmt.Errorf("Pattern Array empty") } switch typedPatternElement := patternArray[0].(type) { case map[string]interface{}: // This is special case, because maps in arrays can have anchors that must be // processed with the special way affecting the entire array - arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path) - res.MergeWith(&arrayResult) + err := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path) + if err != nil { + return err + } default: // In all other cases - detect type and handle each array element with validateResourceElement for i, patternElement := range patternArray { currentPath := path + strconv.Itoa(i) + "/" - elementResult := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath) - res.MergeWith(&elementResult) + err := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath) + if err != nil { + return err + } } } - return res + return nil } -func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, result.RuleApplicationResult) { - res := result.NewRuleApplicationResult("") +func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, error) { var foundValue interface{} referencePattern = strings.Trim(referencePattern, "$()") @@ -160,41 +161,39 @@ func actualizePattern(origPattern interface{}, referencePattern, absolutePath st referencePattern = referencePattern[len(operator):] if len(referencePattern) == 0 { - res.FailWithMessagef("Expected path. Found empty reference") - return nil, res + return nil, errors.New("Expected path. Found empty reference") } actualPath := FormAbsolutePath(referencePattern, absolutePath) - valFromReference, res := getValueFromReference(origPattern, actualPath) - - if result.Failed == res.Reason { - return nil, res + valFromReference, err := getValueFromReference(origPattern, actualPath) + if err != nil { + return err, nil } - + //TODO validate this if operator == Equal { //if operator does not exist return raw value - return valFromReference, res + return valFromReference, nil } - foundValue, res = valFromReferenceToString(valFromReference, string(operator)) - - return string(operator) + foundValue.(string), res + foundValue, err = valFromReferenceToString(valFromReference, string(operator)) + if err != nil { + return "", err + } + return string(operator) + foundValue.(string), nil } //Parse value to string -func valFromReferenceToString(value interface{}, operator string) (string, result.RuleApplicationResult) { - res := result.NewRuleApplicationResult("") +func valFromReferenceToString(value interface{}, operator string) (string, error) { switch typed := value.(type) { case string: - return typed, res + return typed, nil case int, int64: - return fmt.Sprintf("%d", value), res + return fmt.Sprintf("%d", value), nil case float64: - return fmt.Sprintf("%f", value), res + return fmt.Sprintf("%f", value), nil default: - res.FailWithMessagef("Incorrect expression. Operator %s does not match with value: %v", operator, value) - return "", res + return "", fmt.Errorf("Incorrect expression. Operator %s does not match with value: %v", operator, value) } } @@ -207,7 +206,7 @@ func FormAbsolutePath(referencePath, absolutePath string) string { } //Prepares original pattern, path to value, and call traverse function -func getValueFromReference(origPattern interface{}, reference string) (interface{}, result.RuleApplicationResult) { +func getValueFromReference(origPattern interface{}, reference string) (interface{}, error) { originalPatternMap := origPattern.(map[string]interface{}) reference = reference[1:len(reference)] statements := strings.Split(reference, "/") @@ -215,14 +214,13 @@ func getValueFromReference(origPattern interface{}, reference string) (interface return getValueFromPattern(originalPatternMap, statements, 0) } -func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, result.RuleApplicationResult) { - res := result.NewRuleApplicationResult("") +func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, error) { for key, pattern := range patternMap { rawKey := getRawKeyIfWrappedWithAttributes(key) if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 { - return pattern, res + return pattern, nil } else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 { continue } @@ -233,21 +231,20 @@ func getValueFromPattern(patternMap map[string]interface{}, keys []string, curre for i, value := range typedPattern { resourceMap, ok := value.(map[string]interface{}) if !ok { - res.FailWithMessagef("Pattern and resource have different structures. Expected %T, found %T", pattern, value) - return nil, res + return nil, fmt.Errorf("Pattern and resource have different structures. Expected %T, found %T", pattern, value) } if keys[currentKeyIndex+1] == strconv.Itoa(i) { return getValueFromPattern(resourceMap, keys, currentKeyIndex+2) } - res.FailWithMessagef("Reference to non-existent place in the document") + return nil, errors.New("Reference to non-existent place in the document") } } - res.FailWithMessagef("Reference to non-existent place in the document") + return nil, errors.New("Reference to non-existent place in the document") case map[string]interface{}: if keys[currentKeyIndex] == rawKey { return getValueFromPattern(typedPattern, keys, currentKeyIndex+1) } - res.FailWithMessagef("Reference to non-existent place in the document") + return nil, errors.New("Reference to non-existent place in the document") case string, float64, int, int64, bool, nil: continue } @@ -261,13 +258,12 @@ func getValueFromPattern(patternMap map[string]interface{}, keys []string, curre for _, elem := range keys { path = "/" + elem + path } - res.FailWithMessagef("No value found for specified reference: %s", path) - return nil, res + return nil, fmt.Errorf("No value found for specified reference: %s", path) } // validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic // and then validates each map due to the pattern -func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) result.RuleApplicationResult { +func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) error { anchor, pattern := getAnchorFromMap(patternMap) handler := CreateAnchorHandler(anchor, pattern, path) diff --git a/pkg/info/info.go b/pkg/info/info.go new file mode 100644 index 0000000000..2a94984fed --- /dev/null +++ b/pkg/info/info.go @@ -0,0 +1,96 @@ +package info + +import ( + "fmt" + "strings" +) + +//PolicyInfo defines policy information +type PolicyInfo struct { + Name string + Resource string + Namespace string + success bool + rules []*RuleInfo +} + +//NewPolicyInfo returns a new policy info +func NewPolicyInfo(policyName string, resource string, ns string) *PolicyInfo { + return &PolicyInfo{ + Name: policyName, + Resource: resource, + Namespace: ns, + success: true, // fail to be set explicity + } +} + +//IsSuccessful checks if policy is succesful +// the policy is set to fail, if any of the rules have failed +func (pi *PolicyInfo) IsSuccessful() bool { + return pi.success +} + +//ErrorRules returns error msgs from all rule +func (pi *PolicyInfo) ErrorRules() string { + errorMsgs := []string{} + for _, r := range pi.rules { + if !r.IsSuccessful() { + errorMsgs = append(errorMsgs, r.Msgs...) + } + } + return strings.Join(errorMsgs, ";") +} + +//RuleInfo defines rule struct +type RuleInfo struct { + Name string + Msgs []string + success bool +} + +//NewRuleInfo creates a new RuleInfo +func NewRuleInfo(ruleName string) *RuleInfo { + return &RuleInfo{ + Name: ruleName, + Msgs: []string{}, + success: true, // fail to be set explicity + } +} + +//Fail set the rule as failed +func (ri *RuleInfo) Fail() { + ri.success = false +} + +//IsSuccessful checks if rule is succesful +func (ri *RuleInfo) IsSuccessful() bool { + return ri.success +} + +//Add add msg +func (ri *RuleInfo) Add(msg string) { + ri.Msgs = append(ri.Msgs, msg) +} + +//Addf add msg with args +func (ri *RuleInfo) Addf(msg string, args ...interface{}) { + ri.Msgs = append(ri.Msgs, fmt.Sprintf(msg, args...)) +} + +//RulesSuccesfuly check if the any rule has failed or not +func RulesSuccesfuly(rules []*RuleInfo) bool { + for _, r := range rules { + if !r.success { + return false + } + } + return true +} + +//AddRuleInfos sets the rule information +func (pi *PolicyInfo) AddRuleInfos(rules []*RuleInfo) { + if !RulesSuccesfuly(rules) { + pi.success = false + } + pi.rules = rules +} diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index cc7129155a..a6c0a88a90 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" "time" "github.com/golang/glog" @@ -15,7 +16,7 @@ import ( "github.com/nirmata/kyverno/pkg/config" client "github.com/nirmata/kyverno/pkg/dclient" engine "github.com/nirmata/kyverno/pkg/engine" - "github.com/nirmata/kyverno/pkg/result" + "github.com/nirmata/kyverno/pkg/info" "github.com/nirmata/kyverno/pkg/sharedinformer" tlsutils "github.com/nirmata/kyverno/pkg/tls" v1beta1 "k8s.io/api/admission/v1beta1" @@ -94,7 +95,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { admissionReview.Response.UID = admissionReview.Request.UID - responseJson, err := json.Marshal(admissionReview) + responseJSON, err := json.Marshal(admissionReview) if err != nil { http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError) @@ -102,7 +103,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json; charset=utf-8") - if _, err := w.Write(responseJson); err != nil { + if _, err := w.Write(responseJSON); err != nil { http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) } } @@ -133,43 +134,49 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be policies, err := ws.policyLister.List(labels.NewSelector()) if err != nil { + // Unable to connect to policy Lister to access policies + glog.Error("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied") glog.Warning(err) - return nil + return &v1beta1.AdmissionResponse{ + Allowed: true, + } } - admissionResult := result.NewAdmissionResult(string(request.UID)) var allPatches []engine.PatchBytes + policyInfos := []*info.PolicyInfo{} for _, policy := range policies { // check if policy has a rule for the admission request kind if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) { continue } + rname := engine.ParseNameFromObject(request.Object.Raw) + rns := engine.ParseNamespaceFromObject(request.Object.Raw) + policyInfo := info.NewPolicyInfo(policy.Name, + rname, + rns) glog.V(3).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", - request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation) + request.Kind.Kind, rns, rname, request.UID, request.Operation) glog.Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules)) - policyPatches, mutationResult := engine.Mutate(*policy, request.Object.Raw, request.Kind) - allPatches = append(allPatches, policyPatches...) - admissionResult = result.Append(admissionResult, mutationResult) - - if mutationError := mutationResult.ToError(); mutationError != nil { - glog.Warningf(mutationError.Error()) + policyPatches, ruleInfos := engine.Mutate(*policy, request.Object.Raw, request.Kind) + policyInfo.AddRuleInfos(ruleInfos) + if !policyInfo.IsSuccessful() { + glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) + for _, r := range ruleInfos { + glog.Warning(r.Msgs) + } + } else if len(policyPatches) > 0 { + allPatches = append(allPatches, policyPatches...) + glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns) } - - if len(policyPatches) > 0 { - namespace := engine.ParseNamespaceFromObject(request.Object.Raw) - name := engine.ParseNameFromObject(request.Object.Raw) - glog.Infof("Mutation from policy %s has applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name) - } - glog.Info(admissionResult.String()) + policyInfos = append(policyInfos, policyInfo) } - message := "\n" + admissionResult.String() - - if admissionResult.GetReason() == result.Success { + ok, msg := isAdmSuccesful(policyInfos) + if ok { patchType := v1beta1.PatchTypeJSONPatch return &v1beta1.AdmissionResponse{ Allowed: true, @@ -177,68 +184,155 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be PatchType: &patchType, } } - return &v1beta1.AdmissionResponse{ Allowed: false, Result: &metav1.Status{ - Message: message, + Message: msg, }, } } +func isAdmSuccesful(policyInfos []*info.PolicyInfo) (bool, string) { + var admSuccess = true + var errMsgs []string + for _, pi := range policyInfos { + if !pi.IsSuccessful() { + admSuccess = false + errMsgs = append(errMsgs, fmt.Sprintf("\nPolicy %s failed with following rules", pi.Name)) + // Get the error rules + errorRules := pi.ErrorRules() + errMsgs = append(errMsgs, errorRules) + } + } + return admSuccess, strings.Join(errMsgs, ";") +} + // HandleValidation handles validating webhook admission request +// If there are no errors in validating rule we apply generation rules func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { + policyInfos := []*info.PolicyInfo{} policies, err := ws.policyLister.List(labels.NewSelector()) if err != nil { + // Unable to connect to policy Lister to access policies + glog.Error("Unable to connect to policy controller to access policies. Validation Rules are NOT being applied") glog.Warning(err) - return nil + return &v1beta1.AdmissionResponse{ + Allowed: true, + } } - admissionResult := result.NewAdmissionResult(string(request.UID)) for _, policy := range policies { if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) { continue } + rname := engine.ParseNameFromObject(request.Object.Raw) + rns := engine.ParseNamespaceFromObject(request.Object.Raw) + + policyInfo := info.NewPolicyInfo(policy.Name, + rname, + rns) glog.V(3).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", - request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation) + request.Kind.Kind, rns, rname, request.UID, request.Operation) glog.Infof("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) - validationResult := engine.Validate(*policy, request.Object.Raw, request.Kind) - admissionResult = result.Append(admissionResult, validationResult) - - if validationError := validationResult.ToError(); validationError != nil { - glog.Warningf(validationError.Error()) + ruleInfos, err := engine.Validate(*policy, request.Object.Raw, request.Kind) + if err != nil { + // This is not policy error + // but if unable to parse request raw resource + // TODO : create event ? dont think so + glog.Error(err) + continue } - glog.Info(admissionResult.String()) + policyInfo.AddRuleInfos(ruleInfos) + + if !policyInfo.IsSuccessful() { + glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) + for _, r := range ruleInfos { + glog.Warning(r.Msgs) + } + } else { + glog.Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns) + } + policyInfos = append(policyInfos, policyInfo) } - message := "\n" + admissionResult.String() - - // Generation loop after all validation succeeded - var response *v1beta1.AdmissionResponse - - if admissionResult.GetReason() == result.Success { - for _, policy := range policies { - engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind) + // If Validation fails then reject the request + ok, msg := isAdmSuccesful(policyInfos) + if !ok { + return &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Message: msg, + }, } - glog.V(3).Info("Validation is successful") + } + // Process Generation + return ws.HandleGeneration(request) +} - response = &v1beta1.AdmissionResponse{ +//HandleGeneration handles application of generation rules +func (ws *WebhookServer) HandleGeneration(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { + if request.Kind.Kind != "Namespace" { + return &v1beta1.AdmissionResponse{ + Allowed: true, + } + } + policyInfos := []*info.PolicyInfo{} + + policies, err := ws.policyLister.List(labels.NewSelector()) + if err != nil { + // Unable to connect to policy Lister to access policies + glog.Error("Unable to connect to policy controller to access policies. Generation Rules are NOT being applied") + glog.Warning(err) + return &v1beta1.AdmissionResponse{ + Allowed: true, + } + } + for _, policy := range policies { + + if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) { + continue + } + rname := engine.ParseNameFromObject(request.Object.Raw) + rns := engine.ParseNamespaceFromObject(request.Object.Raw) + + policyInfo := info.NewPolicyInfo(policy.Name, + rname, + rns) + glog.V(3).Infof("Handling generation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", + request.Kind.Kind, rns, rname, request.UID, request.Operation) + glog.Infof("Applying policy %s with generation %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) + + ruleInfos := engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind) + policyInfo.AddRuleInfos(ruleInfos) + if !policyInfo.IsSuccessful() { + glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) + for _, r := range ruleInfos { + glog.Warning(r.Msgs) + } + } else { + glog.Infof("Generation from policy %s has succesfully applied to %s %s/%s", policy.Name, request.Kind.Kind, rns, rname) + } + policyInfos = append(policyInfos, policyInfo) + } + ok, msg := isAdmSuccesful(policyInfos) + if ok { + glog.V(3).Info("Generation is successful") + return &v1beta1.AdmissionResponse{ Allowed: true, } } else { - response = &v1beta1.AdmissionResponse{ + return &v1beta1.AdmissionResponse{ Allowed: false, Result: &metav1.Status{ - Message: message, + Message: msg, }, } } - return response } // bodyToAdmissionReview creates AdmissionReview object from request body diff --git a/vendor/github.com/evanphx/json-patch/patch.go b/vendor/github.com/evanphx/json-patch/patch.go index c9cf590216..1b5f95e611 100644 --- a/vendor/github.com/evanphx/json-patch/patch.go +++ b/vendor/github.com/evanphx/json-patch/patch.go @@ -6,6 +6,8 @@ import ( "fmt" "strconv" "strings" + + "github.com/pkg/errors" ) const ( @@ -24,6 +26,14 @@ var ( AccumulatedCopySizeLimit int64 = 0 ) +var ( + ErrTestFailed = errors.New("test failed") + ErrMissing = errors.New("missing value") + ErrUnknownType = errors.New("unknown object type") + ErrInvalid = errors.New("invalid state detected") + ErrInvalidIndex = errors.New("invalid index referenced") +) + type lazyNode struct { raw *json.RawMessage doc partialDoc @@ -31,10 +41,11 @@ type lazyNode struct { which int } -type operation map[string]*json.RawMessage +// Operation is a single JSON-Patch step, such as a single 'add' operation. +type Operation map[string]*json.RawMessage -// Patch is an ordered collection of operations. -type Patch []operation +// Patch is an ordered collection of Operations. +type Patch []Operation type partialDoc map[string]*lazyNode type partialArray []*lazyNode @@ -59,7 +70,7 @@ func (n *lazyNode) MarshalJSON() ([]byte, error) { case eAry: return json.Marshal(n.ary) default: - return nil, fmt.Errorf("Unknown type") + return nil, ErrUnknownType } } @@ -91,7 +102,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) { } if n.raw == nil { - return nil, fmt.Errorf("Unable to unmarshal nil pointer as partial document") + return nil, ErrInvalid } err := json.Unmarshal(*n.raw, &n.doc) @@ -110,7 +121,7 @@ func (n *lazyNode) intoAry() (*partialArray, error) { } if n.raw == nil { - return nil, fmt.Errorf("Unable to unmarshal nil pointer as partial array") + return nil, ErrInvalid } err := json.Unmarshal(*n.raw, &n.ary) @@ -227,7 +238,8 @@ func (n *lazyNode) equal(o *lazyNode) bool { return true } -func (o operation) kind() string { +// Kind reads the "op" field of the Operation. +func (o Operation) Kind() string { if obj, ok := o["op"]; ok && obj != nil { var op string @@ -243,39 +255,41 @@ func (o operation) kind() string { return "unknown" } -func (o operation) path() string { +// Path reads the "path" field of the Operation. +func (o Operation) Path() (string, error) { if obj, ok := o["path"]; ok && obj != nil { var op string err := json.Unmarshal(*obj, &op) if err != nil { - return "unknown" + return "unknown", err } - return op + return op, nil } - return "unknown" + return "unknown", errors.Wrapf(ErrMissing, "operation missing path field") } -func (o operation) from() string { +// From reads the "from" field of the Operation. +func (o Operation) From() (string, error) { if obj, ok := o["from"]; ok && obj != nil { var op string err := json.Unmarshal(*obj, &op) if err != nil { - return "unknown" + return "unknown", err } - return op + return op, nil } - return "unknown" + return "unknown", errors.Wrapf(ErrMissing, "operation, missing from field") } -func (o operation) value() *lazyNode { +func (o Operation) value() *lazyNode { if obj, ok := o["value"]; ok { return newLazyNode(obj) } @@ -283,6 +297,23 @@ func (o operation) value() *lazyNode { return nil } +// ValueInterface decodes the operation value into an interface. +func (o Operation) ValueInterface() (interface{}, error) { + if obj, ok := o["value"]; ok && obj != nil { + var v interface{} + + err := json.Unmarshal(*obj, &v) + + if err != nil { + return nil, err + } + + return v, nil + } + + return nil, errors.Wrapf(ErrMissing, "operation, missing value field") +} + func isArray(buf []byte) bool { Loop: for _, c := range buf { @@ -359,7 +390,7 @@ func (d *partialDoc) get(key string) (*lazyNode, error) { func (d *partialDoc) remove(key string) error { _, ok := (*d)[key] if !ok { - return fmt.Errorf("Unable to remove nonexistent key: %s", key) + return errors.Wrapf(ErrMissing, "Unable to remove nonexistent key: %s", key) } delete(*d, key) @@ -385,7 +416,7 @@ func (d *partialArray) add(key string, val *lazyNode) error { idx, err := strconv.Atoi(key) if err != nil { - return err + return errors.Wrapf(err, "value was not a proper array index: '%s'", key) } sz := len(*d) + 1 @@ -395,12 +426,12 @@ func (d *partialArray) add(key string, val *lazyNode) error { cur := *d if idx >= len(ary) { - return fmt.Errorf("Unable to access invalid index: %d", idx) + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } if SupportNegativeIndices { if idx < -len(ary) { - return fmt.Errorf("Unable to access invalid index: %d", idx) + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } if idx < 0 { @@ -424,7 +455,7 @@ func (d *partialArray) get(key string) (*lazyNode, error) { } if idx >= len(*d) { - return nil, fmt.Errorf("Unable to access invalid index: %d", idx) + return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } return (*d)[idx], nil @@ -439,12 +470,12 @@ func (d *partialArray) remove(key string) error { cur := *d if idx >= len(cur) { - return fmt.Errorf("Unable to access invalid index: %d", idx) + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } if SupportNegativeIndices { if idx < -len(cur) { - return fmt.Errorf("Unable to access invalid index: %d", idx) + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } if idx < 0 { @@ -462,140 +493,189 @@ func (d *partialArray) remove(key string) error { } -func (p Patch) add(doc *container, op operation) error { - path := op.path() - - con, key := findObject(doc, path) - - if con == nil { - return fmt.Errorf("jsonpatch add operation does not apply: doc is missing path: \"%s\"", path) - } - - return con.add(key, op.value()) -} - -func (p Patch) remove(doc *container, op operation) error { - path := op.path() - - con, key := findObject(doc, path) - - if con == nil { - return fmt.Errorf("jsonpatch remove operation does not apply: doc is missing path: \"%s\"", path) - } - - return con.remove(key) -} - -func (p Patch) replace(doc *container, op operation) error { - path := op.path() - - con, key := findObject(doc, path) - - if con == nil { - return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing path: %s", path) - } - - _, ok := con.get(key) - if ok != nil { - return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing key: %s", path) - } - - return con.set(key, op.value()) -} - -func (p Patch) move(doc *container, op operation) error { - from := op.from() - - con, key := findObject(doc, from) - - if con == nil { - return fmt.Errorf("jsonpatch move operation does not apply: doc is missing from path: %s", from) - } - - val, err := con.get(key) +func (p Patch) add(doc *container, op Operation) error { + path, err := op.Path() if err != nil { - return err + return errors.Wrapf(ErrMissing, "add operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "add operation does not apply: doc is missing path: \"%s\"", path) + } + + err = con.add(key, op.value()) + if err != nil { + return errors.Wrapf(err, "error in add for path: '%s'", path) + } + + return nil +} + +func (p Patch) remove(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(ErrMissing, "remove operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "remove operation does not apply: doc is missing path: \"%s\"", path) } err = con.remove(key) if err != nil { - return err + return errors.Wrapf(err, "error in remove for path: '%s'", path) } - path := op.path() - - con, key = findObject(doc, path) - - if con == nil { - return fmt.Errorf("jsonpatch move operation does not apply: doc is missing destination path: %s", path) - } - - return con.add(key, val) + return nil } -func (p Patch) test(doc *container, op operation) error { - path := op.path() +func (p Patch) replace(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(err, "replace operation failed to decode path") + } con, key := findObject(doc, path) if con == nil { - return fmt.Errorf("jsonpatch test operation does not apply: is missing path: %s", path) + return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing path: %s", path) + } + + _, ok := con.get(key) + if ok != nil { + return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing key: %s", path) + } + + err = con.set(key, op.value()) + if err != nil { + return errors.Wrapf(err, "error in remove for path: '%s'", path) + } + + return nil +} + +func (p Patch) move(doc *container, op Operation) error { + from, err := op.From() + if err != nil { + return errors.Wrapf(err, "move operation failed to decode from") + } + + con, key := findObject(doc, from) + + if con == nil { + return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing from path: %s", from) } val, err := con.get(key) - if err != nil { - return err + return errors.Wrapf(err, "error in move for path: '%s'", key) + } + + err = con.remove(key) + if err != nil { + return errors.Wrapf(err, "error in move for path: '%s'", key) + } + + path, err := op.Path() + if err != nil { + return errors.Wrapf(err, "move operation failed to decode path") + } + + con, key = findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing destination path: %s", path) + } + + err = con.add(key, val) + if err != nil { + return errors.Wrapf(err, "error in move for path: '%s'", path) + } + + return nil +} + +func (p Patch) test(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(err, "test operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "test operation does not apply: is missing path: %s", path) + } + + val, err := con.get(key) + if err != nil { + return errors.Wrapf(err, "error in test for path: '%s'", path) } if val == nil { if op.value().raw == nil { return nil } - return fmt.Errorf("Testing value %s failed", path) + return errors.Wrapf(ErrTestFailed, "testing value %s failed", path) } else if op.value() == nil { - return fmt.Errorf("Testing value %s failed", path) + return errors.Wrapf(ErrTestFailed, "testing value %s failed", path) } if val.equal(op.value()) { return nil } - return fmt.Errorf("Testing value %s failed", path) + return errors.Wrapf(ErrTestFailed, "testing value %s failed", path) } -func (p Patch) copy(doc *container, op operation, accumulatedCopySize *int64) error { - from := op.from() +func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64) error { + from, err := op.From() + if err != nil { + return errors.Wrapf(err, "copy operation failed to decode from") + } con, key := findObject(doc, from) if con == nil { - return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing from path: %s", from) + return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing from path: %s", from) } val, err := con.get(key) if err != nil { - return err + return errors.Wrapf(err, "error in copy for from: '%s'", from) } - path := op.path() + path, err := op.Path() + if err != nil { + return errors.Wrapf(ErrMissing, "copy operation failed to decode path") + } con, key = findObject(doc, path) if con == nil { - return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing destination path: %s", path) + return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path) } valCopy, sz, err := deepCopy(val) if err != nil { - return err + return errors.Wrapf(err, "error while performing deep copy") } + (*accumulatedCopySize) += int64(sz) if AccumulatedCopySizeLimit > 0 && *accumulatedCopySize > AccumulatedCopySizeLimit { return NewAccumulatedCopySizeError(AccumulatedCopySizeLimit, *accumulatedCopySize) } - return con.add(key, valCopy) + err = con.add(key, valCopy) + if err != nil { + return errors.Wrapf(err, "error while adding value during copy") + } + + return nil } // Equal indicates if 2 JSON documents have the same structural equality. @@ -651,7 +731,7 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) { var accumulatedCopySize int64 for _, op := range p { - switch op.kind() { + switch op.Kind() { case "add": err = p.add(&pd, op) case "remove": @@ -665,7 +745,7 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) { case "copy": err = p.copy(&pd, op, &accumulatedCopySize) default: - err = fmt.Errorf("Unexpected kind: %s", op.kind()) + err = fmt.Errorf("Unexpected kind: %s", op.Kind()) } if err != nil {