1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

introduce info struct for engine responses

This commit is contained in:
shivkumar dudhani 2019-06-25 18:16:02 -07:00
parent 1f8b370f5a
commit b7655ae747
9 changed files with 659 additions and 418 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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 {
glog.V(3).Info("Not applicable on specified resource kind%s", gvk.Kind)
continue
}
// 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)
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)
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
}

View file

@ -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)
patch, err := replaceSubtree(overlay, path)
if err != nil {
return nil, err
}
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) {
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() {
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() {
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() {
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() {
patch, err := insertSubtree(overlayElement, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patch)
}
}
return appliedPatches, nil
}
return appliedPatches, overlayResult
}
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() {
patches, err := applyOverlay(resourceElement, overlay, currentPath)
if err != nil {
return nil, err
}
appliedPatches = append(appliedPatches, patches...)
}
}
return appliedPatches, nil
}
return appliedPatches, overlayResult
}
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 ""
}

View file

@ -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" {
errs = append(errs, err)
continue
}
message := fmt.Sprintf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err)
res.Messages = append(res.Messages, message)
continue
}
allPatches = append(allPatches, patchRaw)
}
return allPatches, res
return allPatches, errs
}
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array

View file

@ -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")
}
ris = append(ris, ri)
}
policyResult = result.Append(policyResult, &ruleApplicationResult)
}
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)

96
pkg/info/info.go Normal file
View file

@ -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
}

View file

@ -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)
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...)
admissionResult = result.Append(admissionResult, mutationResult)
if mutationError := mutationResult.ToError(); mutationError != nil {
glog.Warningf(mutationError.Error())
glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns)
}
policyInfos = append(policyInfos, policyInfo)
}
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())
}
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()
// If Validation fails then reject the request
ok, msg := isAdmSuccesful(policyInfos)
if !ok {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: msg,
},
}
}
// Process Generation
return ws.HandleGeneration(request)
}
// Generation loop after all validation succeeded
var 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{}
if admissionResult.GetReason() == result.Success {
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 {
engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind)
}
glog.V(3).Info("Validation is successful")
response = &v1beta1.AdmissionResponse{
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

View file

@ -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 nil
}
return con.add(key, val)
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")
}
func (p Patch) test(doc *container, op operation) error {
path := op.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 {