diff --git a/README.md b/README.md index 7a2e4b38ab..f0c8808da3 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,10 @@ metadata: spec: rules: - name: check-pod-resources - resource: - kinds: - - Pod + match: + resources: + kinds: + - Pod validate: message: "CPU and memory resource requests and limits are required" pattern: @@ -67,9 +68,10 @@ metadata: spec: rules: - name: set-image-pull-policy - resource: - kinds: - - Deployment + match: + resources: + kinds: + - Deployment mutate: overlay: spec: @@ -94,12 +96,13 @@ metadata: spec: rules: - name: "zk-kafka-address" - resource: - kinds: - - Namespace - selector: - matchExpressions: - - {key: kafka, operator: Exists} + match: + resources: + kinds: + - Namespace + selector: + matchExpressions: + - {key: kafka, operator: Exists} generate: kind: ConfigMap name: zk-kafka-address diff --git a/examples/policy_generate_networkPolicy.yaml b/examples/policy_generate_networkPolicy.yaml index 6c4ea2bf55..7f76d46312 100644 --- a/examples/policy_generate_networkPolicy.yaml +++ b/examples/policy_generate_networkPolicy.yaml @@ -1,7 +1,7 @@ apiVersion: kyverno.io/v1alpha1 kind: Policy metadata: - name: "default-networkpolicy" + name: defaultgeneratenetworkpolicy spec: rules: - name: "default-networkpolicy" @@ -12,7 +12,7 @@ spec: name: "devtest" generate: kind: NetworkPolicy - name: default-networkpolicy + name: defaultnetworkpolicy data: spec: # select all pods in the namespace diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 21f4e71747..46725b8cc2 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -78,6 +78,16 @@ func (p *Policy) updatePolicy(obj *Policy, ruleType pinfo.RuleType) bool { updates := false // Check Mutation rules switch ruleType { + case pinfo.All: + if p.compareMutationRules(obj.MutationRules) { + updates = true + } + if p.compareValidationRules(obj.ValidationRules) { + updates = true + } + if p.compareGenerationRules(obj.GenerationRules) { + updates = true + } case pinfo.Mutation: if p.compareMutationRules(obj.MutationRules) { updates = true @@ -214,6 +224,59 @@ func ParseAnnotationsFromObject(bytes []byte) map[string]string { return ann } +func PatchAnnotations(ann map[string]string, pi *pinfo.PolicyInfo, ruleType pinfo.RuleType) ([]byte, error) { + if ruleType != pinfo.All && !pi.ContainsRuleType(ruleType) { + // the rule was not proceesed in the current policy application + return nil, nil + } + // transform the PolicyInfo to anotation struct + policyObj := newAnnotationForPolicy(pi) + if ann == nil { + ann = make(map[string]string, 0) + policyByte, err := json.Marshal(policyObj) + if err != nil { + return nil, err + } + // create a json patch to add annotation object + ann[BuildKeyString(pi.Name)] = string(policyByte) + // patch adds the annotation map with the policy information + jsonPatch, err := createAddJSONPatchMap(ann) + return jsonPatch, err + } + // if the annotations map already exists then we need to update it by adding a patch to the field inside the annotation + cPolicy, ok := ann[BuildKey(pi.Name)] + if !ok { + // annotations does not contain the policy + policyByte, err := json.Marshal(policyObj) + if err != nil { + return nil, err + } + jsonPatch, err := createAddJSONPatch(BuildKey(pi.Name), string(policyByte)) + return jsonPatch, err + } + // an annotaion exists for the policy, we need to update the information if anything has changed + cPolicyObj := Policy{} + err := json.Unmarshal([]byte(cPolicy), &cPolicyObj) + if err != nil { + // error while unmarshallign the content + return nil, err + } + // update policy information inside the annotation + // 1> policy status + // 2> rule (name, status,changes,type) + update := cPolicyObj.updatePolicy(policyObj, ruleType) + if !update { + // there is not update, so we dont + return nil, nil + } + policyByte, err := json.Marshal(cPolicyObj) + if err != nil { + return nil, err + } + jsonPatch, err := createAddJSONPatch(BuildKey(pi.Name), string(policyByte)) + return jsonPatch, err +} + //AddPolicyJSONPatch generate JSON Patch to add policy informatino JSON patch func AddPolicyJSONPatch(ann map[string]string, pi *pinfo.PolicyInfo, ruleType pinfo.RuleType) (map[string]string, []byte, error) { if !pi.ContainsRuleType(ruleType) { diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 471aa1d61a..40b5e283c8 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -5,8 +5,6 @@ import ( "reflect" "time" - jsonpatch "github.com/evanphx/json-patch" - "github.com/nirmata/kyverno/pkg/annotations" "github.com/nirmata/kyverno/pkg/info" "github.com/nirmata/kyverno/pkg/utils" @@ -207,7 +205,6 @@ func (pc *PolicyController) syncHandler(obj interface{}) error { func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) { for _, pi := range policyInfos { - var patch []byte //get resource obj, err := pc.client.GetResource(pi.RKind, pi.RNamespace, pi.RName) if err != nil { @@ -216,61 +213,14 @@ func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) { } // add annotation for policy application ann := obj.GetAnnotations() - // Mutation rules - ann, mpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Mutation) - if err != nil { - glog.Error(err) - continue - } - // Validation rules - ann, vpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Validation) - if err != nil { - glog.Error(err) - } - - // Generation rules - ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Generation) - if err != nil { - glog.Error(err) - } - - if mpatch == nil && vpatch == nil && gpatch == nil { - //nothing to patch - continue - } - // merge the patches - if mpatch != nil && vpatch != nil { - patch, err = jsonpatch.MergePatch(mpatch, vpatch) - if err != nil { - glog.Error(err) - continue - } - } - - if mpatch == nil { - patch = vpatch - } else { - patch = mpatch - } - - if gpatch != nil { - // generation - if patch != nil { - patch, err = jsonpatch.MergePatch(patch, gpatch) - if err != nil { - glog.Error(err) - continue - } - } else { - patch = gpatch - } - } - + // if annotations are nil then create a map and patch + // else + // add the exact patch + patch, err := annotations.PatchAnnotations(ann, pi, info.All) if patch == nil { + /// nothing to patch return } - - // add the anotation to the resource _, err = pc.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, patch) if err != nil { glog.Error(err) diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 2bc02fc2f5..f34479b043 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -2,13 +2,14 @@ package engine import ( "encoding/json" - "errors" + "fmt" "github.com/golang/glog" v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/info" "github.com/nirmata/kyverno/pkg/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -21,7 +22,7 @@ func Generate(client *client.Client, policy *v1alpha1.Policy, ns unstructured.Un continue } ri := info.NewRuleInfo(rule.Name, info.Generation) - err := applyRuleGenerator(client, ns, rule.Generation) + err := applyRuleGenerator(client, ns, rule.Generation, policy.GetCreationTimestamp()) if err != nil { ri.Fail() ri.Addf("Rule %s: Failed to apply rule generator, err %v.", rule.Name, err) @@ -34,11 +35,15 @@ func Generate(client *client.Client, policy *v1alpha1.Policy, ns unstructured.Un return ris } -func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen *v1alpha1.Generation) error { +func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen *v1alpha1.Generation, policyCreationTime metav1.Time) error { var err error resource := &unstructured.Unstructured{} var rdata map[string]interface{} - + // To manage existing resource , we compare the creation time for the default resource to be generate and policy creation time + processExisting := func() bool { + nsCreationTime := ns.GetCreationTimestamp() + return nsCreationTime.Before(&policyCreationTime) + }() if gen.Data != nil { // 1> Check if resource exists obj, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name) @@ -51,7 +56,7 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen return err } if !ok { - return errors.New("rule configuration not present in resource") + return fmt.Errorf("rule configuration not present in resource %s/%s", ns.GetName(), gen.Name) } return nil } @@ -74,12 +79,15 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen } rdata = resource.UnstructuredContent() } + if processExisting { + // for existing resources we generate an error which indirectly generates a policy violation + return fmt.Errorf("resource %s not found in existing namespace %s", gen.Name, ns.GetName()) + } resource.SetUnstructuredContent(rdata) resource.SetName(gen.Name) resource.SetNamespace(ns.GetName()) // Reset resource version resource.SetResourceVersion("") - _, err = client.CreateResource(gen.Kind, ns.GetName(), resource, false) if err != nil { return err diff --git a/pkg/gencontroller/generation.go b/pkg/gencontroller/generation.go index 72989a8cc5..81ff8a3e4e 100644 --- a/pkg/gencontroller/generation.go +++ b/pkg/gencontroller/generation.go @@ -140,7 +140,7 @@ func (c *Controller) createAnnotations(pi *info.PolicyInfo) { // add annotation for policy application ann := obj.GetAnnotations() // Generation rules - ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Generation) + gpatch, err := annotations.PatchAnnotations(ann, pi, info.Generation) if err != nil { glog.Error(err) return diff --git a/pkg/info/info.go b/pkg/info/info.go index fbde19e160..8d61b764f0 100644 --- a/pkg/info/info.go +++ b/pkg/info/info.go @@ -99,6 +99,7 @@ const ( Mutation RuleType = iota Validation Generation + All ) func (ri RuleType) String() string { @@ -106,6 +107,7 @@ func (ri RuleType) String() string { "Mutation", "Validation", "Generation", + "All", }[ri] } diff --git a/pkg/webhooks/report.go b/pkg/webhooks/report.go index d40a291efb..97116c87fa 100644 --- a/pkg/webhooks/report.go +++ b/pkg/webhooks/report.go @@ -68,7 +68,7 @@ func addAnnotationsToResource(rawResource []byte, pi *info.PolicyInfo, ruleType } // get annotations ann := annotations.ParseAnnotationsFromObject(rawResource) - ann, patch, err := annotations.AddPolicyJSONPatch(ann, pi, ruleType) + patch, err := annotations.PatchAnnotations(ann, pi, ruleType) if err != nil { glog.Error(err) return nil