diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 2eb42a19d2..fe367a2def 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -56,11 +56,8 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) EngineRes ruleInfo.Addf("Rule %s: Overlay succesfully applied.", rule.Name) - // merge the json patches - patch := JoinPatches(rulePatches) - // strip slashes from string - ruleInfo.Changes = string(patch) + ruleInfo.Patches = rulePatches allPatches = append(allPatches, rulePatches...) glog.V(4).Infof("overlay applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName()) @@ -83,6 +80,8 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) EngineRes } else { glog.V(4).Infof("patches applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName()) ruleInfo.Addf("Patches succesfully applied.") + + ruleInfo.Patches = rulePatches allPatches = append(allPatches, rulePatches...) } } @@ -96,6 +95,11 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) EngineRes } patchedResource, err := ConvertToUnstructured(patchedDocument) + if err != nil { + glog.Errorf("Failed to convert patched resource to unstructuredtype, err%v\n:", err) + return EngineResponse{PatchedResource: resource} + } + return EngineResponse{ Patches: allPatches, PatchedResource: *patchedResource, diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 29e017befa..2d8e36560c 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -20,7 +20,7 @@ import ( func Validate(policy kyverno.Policy, resource unstructured.Unstructured) EngineResponse { resourceRaw, err := resource.MarshalJSON() if err != nil { - glog.V(4).Infof("unable to marshal resource : %v\n", err) + glog.V(4).Infof("Skip processing validating rule, unable to marshal resource : %v\n", err) return EngineResponse{PatchedResource: resource} } diff --git a/pkg/info/info.go b/pkg/info/info.go index d8b742e350..b8aaa57db3 100644 --- a/pkg/info/info.go +++ b/pkg/info/info.go @@ -104,7 +104,7 @@ type RuleInfo struct { Name string RuleType RuleType Msgs []string - Changes string // this will store the mutation patch being applied by the rule + Patches [][]byte // this will store the mutation patch being applied by the rule success bool } diff --git a/pkg/webhooks/annotations.go b/pkg/webhooks/annotations.go new file mode 100644 index 0000000000..41a37633f7 --- /dev/null +++ b/pkg/webhooks/annotations.go @@ -0,0 +1,113 @@ +package webhooks + +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + jsonpatch "github.com/evanphx/json-patch" + "github.com/golang/glog" + "github.com/nirmata/kyverno/pkg/info" +) + +const ( + policyAnnotation = "policies.kyverno.io" + // lastAppliedPatches = policyAnnotation + "last-applied-patches" +) + +type policyPatch struct { + PolicyName string `json:"policyname"` + // RulePatches []string `json:"patches"` + RulePatches interface{} `json:"patches"` +} + +type rulePatch struct { + RuleName string `json:"rulename"` + Op string `json:"op"` + Path string `json:"path"` +} + +type response struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value"` +} + +func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos []info.PolicyInfo) []byte { + annots := resource.GetAnnotations() + if annots == nil { + annots = map[string]string{} + } + + var patchResponse response + value := annotationFromPolicies(policyInfos) + if _, ok := annots[policyAnnotation]; ok { + // create update patch string + patchResponse = response{ + Op: "replace", + Path: "/metadata/annotations/" + policyAnnotation, + Value: string(value), + } + } else { + patchResponse = response{ + Op: "add", + Path: "/metadata/annotations", + Value: map[string]string{policyAnnotation: string(value)}, + } + } + + patchByte, _ := json.Marshal(patchResponse) + + // check the patch + _, err := jsonpatch.DecodePatch([]byte("[" + string(patchByte) + "]")) + if err != nil { + glog.Errorf("Failed to make patch from annotation'%s', err: %v\n ", string(patchByte), err) + } + + return patchByte +} + +func annotationFromPolicies(policyInfos []info.PolicyInfo) []byte { + var policyPatches []policyPatch + for _, policyInfo := range policyInfos { + var pp policyPatch + + pp.PolicyName = policyInfo.Name + pp.RulePatches = annotationFromPolicy(policyInfo) + policyPatches = append(policyPatches, pp) + } + + result, _ := json.Marshal(policyPatches) + + return result +} + +func annotationFromPolicy(policyInfo info.PolicyInfo) []rulePatch { + if !policyInfo.IsSuccessful() { + glog.V(2).Infof("Policy %s failed, skip preparing annotation\n", policyInfo.Name) + return nil + } + + var rulePatches []rulePatch + for _, ruleInfo := range policyInfo.Rules { + + for _, patch := range ruleInfo.Patches { + var patchmap map[string]string + + if err := json.Unmarshal(patch, &patchmap); err != nil { + glog.Errorf("Failed to parse patch bytes, err: %v\n", err) + continue + } + + rp := rulePatch{ + RuleName: ruleInfo.Name, + Op: patchmap["op"], + Path: patchmap["path"]} + + rulePatches = append(rulePatches, rp) + glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches) + } + } + + return rulePatches +} diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index a54a6151f4..88c60afa4a 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -62,18 +62,20 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool } continue } - // build annotations per policy being applied to show the mutation changes - patches = append(patches, engineResponse.Patches...) - glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName()) + patches = append(patches, engineResponse.Patches...) + policyInfos = append(policyInfos, policyInfo) + glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName()) } // ADD ANNOTATIONS - // TODO: merge the annotation patch with the patch response // ADD EVENTS if len(patches) > 0 { eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation) ws.eventGen.Add(eventsInfo...) + + annotation := prepareAnnotationPatches(resource, policyInfos) + patches = append(patches, annotation) } ok, msg := isAdmSuccesful(policyInfos) diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index ddad42e725..56aa4d1970 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -52,7 +52,6 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, res engineResponse := engine.Validate(*policy, resource) if len(engineResponse.RuleInfos) == 0 { - glog.Errorln("Failed to process validate rule, error parsing rawResource") continue }