1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00

Merge pull request #323 from nirmata/refactor_engineResponse

Refactor engine response
This commit is contained in:
Shivkumar Dudhani 2019-08-30 01:17:00 -07:00 committed by GitHub
commit be47523b46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2589 additions and 1383 deletions

View file

@ -17,4 +17,4 @@ spec:
containers:
- name: nginx
image: nginx:latest
# imagePullPolicy: IfNotPresent
imagePullPolicy: Always

View file

@ -13,7 +13,7 @@ spec:
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
readinessProbe:
# successThreshold: 3
successThreshold: 3
exec:
command:
- cat
@ -26,6 +26,7 @@ spec:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
httpHeaders:
- name: Custom-Header
value: Awesome

View file

@ -2,7 +2,6 @@ package engine
import (
"encoding/json"
"errors"
"time"
"fmt"
@ -10,7 +9,6 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/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"
@ -18,43 +16,49 @@ import (
)
//Generate apply generation rules on a resource
func Generate(client *client.Client, policy kyverno.Policy, ns unstructured.Unstructured) (response EngineResponse) {
func Generate(client *client.Client, policy kyverno.Policy, ns unstructured.Unstructured) (response EngineResponseNew) {
startTime := time.Now()
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = ns.GetName()
response.PolicyResponse.Resource.Kind = ns.GetKind()
response.PolicyResponse.Resource.APIVersion = ns.GetAPIVersion()
}()
glog.V(4).Infof("started applying generation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
response.ExecutionTime = time.Since(startTime)
glog.V(4).Infof("Finished applying generation rules policy %q (%v)", policy.Name, response.ExecutionTime)
glog.V(4).Infof("Generation Rules appplied count %q for policy %q", response.RulesAppliedCount, policy.Name)
response.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying generation rules policy %v (%v)", policy.Name, response.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Generation Rules appplied succesfully count %v for policy %q", response.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.RulesAppliedCount++
response.PolicyResponse.RulesAppliedCount++
}
ris := []info.RuleInfo{}
for _, rule := range policy.Spec.Rules {
if rule.Generation == (kyverno.Generation{}) {
continue
}
glog.V(4).Infof("applying policy %s generate rule %s on resource %s/%s/%s", policy.Name, rule.Name, ns.GetKind(), ns.GetNamespace(), ns.GetName())
ri := info.NewRuleInfo(rule.Name, info.Generation)
err := applyRuleGenerator(client, ns, rule.Generation, policy.GetCreationTimestamp())
if err != nil {
ri.Fail()
ri.Addf("Failed to apply rule %s generator, err %v.", rule.Name, err)
glog.Infof("failed to apply policy %s rule %s on resource %s/%s/%s: %v", policy.Name, rule.Name, ns.GetKind(), ns.GetNamespace(), ns.GetName(), err)
} else {
ri.Addf("Generation succesfully for rule %s", rule.Name)
glog.Infof("succesfully applied policy %s rule %s on resource %s/%s/%s", policy.Name, rule.Name, ns.GetKind(), ns.GetNamespace(), ns.GetName())
}
ris = append(ris, ri)
ruleResponse := applyRuleGenerator(client, ns, rule, policy.GetCreationTimestamp())
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
response.RuleInfos = ris
return response
}
func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen kyverno.Generation, policyCreationTime metav1.Time) error {
func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, rule kyverno.Rule, policyCreationTime metav1.Time) (response RuleResponse) {
startTime := time.Now()
glog.V(4).Infof("started applying generation rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Generation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying generation rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
var err error
resource := &unstructured.Unstructured{}
var rdata map[string]interface{}
@ -63,66 +67,84 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen
nsCreationTime := ns.GetCreationTimestamp()
return nsCreationTime.Before(&policyCreationTime)
}()
if gen.Data != nil {
if rule.Generation.Data != nil {
glog.V(4).Info("generate rule: creates new resource")
// 1> Check if resource exists
obj, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name)
obj, err := client.GetResource(rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
if err == nil {
glog.V(4).Infof("generate rule: resource %s/%s/%s already present. checking if it contains the required configuration", gen.Kind, ns.GetName(), gen.Name)
glog.V(4).Infof("generate rule: resource %s/%s/%s already present. checking if it contains the required configuration", rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
// 2> If already exsists, then verify the content is contained
// found the resource
// check if the rule is create, if yes, then verify if the specified configuration is present in the resource
ok, err := checkResource(gen.Data, obj)
ok, err := checkResource(rule.Generation.Data, obj)
if err != nil {
glog.V(4).Infof("generate rule:: unable to check if configuration %v, is present in resource %s/%s/%s", gen.Data, gen.Kind, ns.GetName(), gen.Name)
return err
glog.V(4).Infof("generate rule:: unable to check if configuration %v, is present in resource %s/%s/%s", rule.Generation.Data, rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
response.Success = false
response.Message = fmt.Sprintf("unable to check if configuration %v, is present in resource %s/%s/%s", rule.Generation.Data, rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
return response
}
if !ok {
glog.V(4).Infof("generate rule:: configuration %v not present in resource %s/%s/%s", gen.Data, gen.Kind, ns.GetName(), gen.Name)
return errors.New("rule configuration not present in resource")
glog.V(4).Infof("generate rule:: configuration %v not present in resource %s/%s/%s", rule.Generation.Data, rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
response.Success = false
response.Message = fmt.Sprintf("configuration %v not present in resource %s/%s/%s", rule.Generation.Data, rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
return response
}
glog.V(4).Infof("generate rule: required configuration %v is present in resource %s/%s/%s", gen.Data, gen.Kind, ns.GetName(), gen.Name)
return nil
response.Success = true
response.Message = fmt.Sprintf("required configuration %v is present in resource %s/%s/%s", rule.Generation.Data, rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
return response
}
rdata, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&gen.Data)
rdata, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&rule.Generation.Data)
if err != nil {
glog.Error(err)
return err
response.Success = false
response.Message = fmt.Sprintf("failed to parse the specified resource spec %v: %v", rule.Generation.Data, err)
return response
}
}
if gen.Clone != (kyverno.CloneFrom{}) {
if rule.Generation.Clone != (kyverno.CloneFrom{}) {
glog.V(4).Info("generate rule: clone resource")
// 1> Check if resource exists
_, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name)
_, err := client.GetResource(rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
if err == nil {
glog.V(4).Infof("generate rule: resource %s/%s/%s already present", gen.Kind, ns.GetName(), gen.Name)
return nil
glog.V(4).Infof("generate rule: resource %s/%s/%s already present", rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
response.Success = true
response.Message = fmt.Sprintf("resource %s/%s/%s already present", rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
return response
}
// 2> If clone already exists return
resource, err = client.GetResource(gen.Kind, gen.Clone.Namespace, gen.Clone.Name)
resource, err = client.GetResource(rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name)
if err != nil {
glog.V(4).Infof("generate rule: clone reference resource %s/%s/%s not present: %v", gen.Kind, gen.Clone.Namespace, gen.Clone.Name, err)
return err
glog.V(4).Infof("generate rule: clone reference resource %s/%s/%s not present: %v", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name, err)
response.Success = false
response.Message = fmt.Sprintf("clone reference resource %s/%s/%s not present: %v", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name, err)
return response
}
glog.V(4).Infof("generate rule: clone reference resource %s/%s/%s present", gen.Kind, gen.Clone.Namespace, gen.Clone.Name)
glog.V(4).Infof("generate rule: clone reference resource %s/%s/%s present", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name)
rdata = resource.UnstructuredContent()
}
if processExisting {
glog.V(4).Infof("resource %s not found in existing namespace %s", rule.Generation.Name, ns.GetName())
response.Success = false
response.Message = fmt.Sprintf("resource %s not found in existing namespace %s", rule.Generation.Name, ns.GetName())
// 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())
return response
}
resource.SetUnstructuredContent(rdata)
resource.SetName(gen.Name)
resource.SetName(rule.Generation.Name)
resource.SetNamespace(ns.GetName())
// Reset resource version
resource.SetResourceVersion("")
_, err = client.CreateResource(gen.Kind, ns.GetName(), resource, false)
_, err = client.CreateResource(rule.Generation.Kind, ns.GetName(), resource, false)
if err != nil {
glog.V(4).Infof("generate rule: unable to create resource %s/%s/%s: %v", gen.Kind, resource.GetNamespace(), resource.GetName(), err)
return err
glog.V(4).Infof("generate rule: unable to create resource %s/%s/%s: %v", rule.Generation.Kind, resource.GetNamespace(), resource.GetName(), err)
response.Success = false
response.Message = fmt.Sprintf("unable to create resource %s/%s/%s: %v", rule.Generation.Kind, resource.GetNamespace(), resource.GetName(), err)
return response
}
glog.V(4).Infof("generate rule: created resource %s/%s/%s", gen.Kind, resource.GetNamespace(), resource.GetName())
return nil
glog.V(4).Infof("generate rule: created resource %s/%s/%s", rule.Generation.Kind, resource.GetNamespace(), resource.GetName())
response.Success = true
response.Message = fmt.Sprintf("created resource %s/%s/%s", rule.Generation.Kind, resource.GetNamespace(), resource.GetName())
return response
}
//checkResource checks if the config is present in th eresource

View file

@ -6,46 +6,155 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// Mutate performs mutation. Overlay first and then mutation patches
func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponse) {
// var response EngineResponse
var allPatches, rulePatches [][]byte
var err error
var errs []error
ris := []info.RuleInfo{}
// func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponse) {
// // var response EngineResponse
// var allPatches, rulePatches [][]byte
// var err error
// var errs []error
// ris := []info.RuleInfo{}
// startTime := time.Now()
// glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
// defer func() {
// response.ExecutionTime = time.Since(startTime)
// glog.V(4).Infof("finished applying mutation rules policy %v (%v)", policy.Name, response.ExecutionTime)
// glog.V(4).Infof("Mutation Rules appplied succesfully count %v for policy %q", response.RulesAppliedCount, policy.Name)
// }()
// incrementAppliedRuleCount := func() {
// // rules applied succesfully count
// response.RulesAppliedCount++
// }
// patchedDocument, err := resource.MarshalJSON()
// if err != nil {
// glog.Errorf("unable to marshal resource : %v\n", err)
// }
// if err != nil {
// glog.V(4).Infof("unable to marshal resource : %v", err)
// response.PatchedResource = resource
// return response
// }
// for _, rule := range policy.Spec.Rules {
// if reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
// continue
// }
// // check if the resource satisfies the filter conditions defined in the rule
// //TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// // dont statisfy a policy rule resource description
// ok := MatchesResourceDescription(resource, rule)
// if !ok {
// glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
// continue
// }
// ruleInfo := info.NewRuleInfo(rule.Name, info.Mutation)
// // Process Overlay
// if rule.Mutation.Overlay != nil {
// // ruleRespone := processOverlay(rule, res)
// rulePatches, err = processOverlay(rule, patchedDocument)
// if err == nil {
// if len(rulePatches) == 0 {
// // if array elements dont match then we skip(nil patch, no error)
// // or if acnohor is defined and doenst match
// // policy is not applicable
// glog.V(4).Info("overlay does not match, so skipping applying rule")
// continue
// }
// ruleInfo.Addf("Rule %s: Overlay succesfully applied.", rule.Name)
// // strip slashes from string
// ruleInfo.Patches = rulePatches
// allPatches = append(allPatches, rulePatches...)
// glog.V(4).Infof("overlay applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
// } else {
// glog.V(4).Infof("failed to apply overlay: %v", err)
// ruleInfo.Fail()
// ruleInfo.Addf("failed to apply overlay: %v", err)
// }
// incrementAppliedRuleCount()
// }
// // Process Patches
// if len(rule.Mutation.Patches) != 0 {
// rulePatches, errs = processPatches(rule, patchedDocument)
// if len(errs) > 0 {
// ruleInfo.Fail()
// for _, err := range errs {
// glog.V(4).Infof("failed to apply patches: %v", err)
// ruleInfo.Addf("patches application has failed, err %v.", err)
// }
// } 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...)
// }
// incrementAppliedRuleCount()
// }
// patchedDocument, err = ApplyPatches(patchedDocument, rulePatches)
// if err != nil {
// glog.Errorf("Failed to apply patches on ruleName=%s, err%v\n:", rule.Name, err)
// }
// ris = append(ris, ruleInfo)
// }
// patchedResource, err := ConvertToUnstructured(patchedDocument)
// if err != nil {
// glog.Errorf("Failed to convert patched resource to unstructuredtype, err%v\n:", err)
// response.PatchedResource = resource
// return response
// }
// response.Patches = allPatches
// response.PatchedResource = *patchedResource
// response.RuleInfos = ris
// return response
// }
//MutateNew ...
func MutateNew(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponseNew) {
startTime := time.Now()
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = resource.GetName()
response.PolicyResponse.Resource.Namespace = resource.GetNamespace()
response.PolicyResponse.Resource.Kind = resource.GetKind()
response.PolicyResponse.Resource.APIVersion = resource.GetAPIVersion()
}()
glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
response.ExecutionTime = time.Since(startTime)
glog.V(4).Infof("finished applying mutation rules policy %v (%v)", policy.Name, response.ExecutionTime)
glog.V(4).Infof("Mutation Rules appplied succesfully count %v for policy %q", response.RulesAppliedCount, policy.Name)
response.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying mutation rules policy %v (%v)", policy.Name, response.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Mutation Rules appplied succesfully count %v for policy %q", response.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.RulesAppliedCount++
response.PolicyResponse.RulesAppliedCount++
}
patchedDocument, err := resource.MarshalJSON()
if err != nil {
glog.Errorf("unable to marshal resource : %v\n", err)
}
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
response.PatchedResource = resource
return response
}
var patchedResource unstructured.Unstructured
for _, rule := range policy.Spec.Rules {
//TODO: to be checked before calling the resources as well
if reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
@ -54,72 +163,27 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) (response
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
ruleInfo := info.NewRuleInfo(rule.Name, info.Mutation)
// Process Overlay
if rule.Mutation.Overlay != nil {
rulePatches, err = processOverlay(rule, patchedDocument)
if err == nil {
if len(rulePatches) == 0 {
// if array elements dont match then we skip(nil patch, no error)
// or if acnohor is defined and doenst match
// policy is not applicable
glog.V(4).Info("overlay does not match, so skipping applying rule")
continue
}
ruleInfo.Addf("Rule %s: Overlay succesfully applied.", rule.Name)
// strip slashes from string
ruleInfo.Patches = rulePatches
allPatches = append(allPatches, rulePatches...)
glog.V(4).Infof("overlay applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
} else {
glog.V(4).Infof("failed to apply overlay: %v", err)
ruleInfo.Fail()
ruleInfo.Addf("failed to apply overlay: %v", err)
var ruleResponse RuleResponse
ruleResponse, patchedResource = processOverlayNew(rule, resource)
if reflect.DeepEqual(ruleResponse, (RuleResponse{})) {
// overlay pattern does not match the resource conditions
continue
}
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
// Process Patches
if len(rule.Mutation.Patches) != 0 {
rulePatches, errs = processPatches(rule, patchedDocument)
if len(errs) > 0 {
ruleInfo.Fail()
for _, err := range errs {
glog.V(4).Infof("failed to apply patches: %v", err)
ruleInfo.Addf("patches application has failed, err %v.", err)
}
} 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...)
}
if rule.Mutation.Patches != nil {
var ruleResponse RuleResponse
ruleResponse, patchedResource = processPatchesNew(rule, resource)
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
patchedDocument, err = ApplyPatches(patchedDocument, rulePatches)
if err != nil {
glog.Errorf("Failed to apply patches on ruleName=%s, err%v\n:", rule.Name, err)
}
ris = append(ris, ruleInfo)
}
patchedResource, err := ConvertToUnstructured(patchedDocument)
if err != nil {
glog.Errorf("Failed to convert patched resource to unstructuredtype, err%v\n:", err)
response.PatchedResource = resource
return response
}
response.Patches = allPatches
response.PatchedResource = *patchedResource
response.RuleInfos = ris
// send the patched resource
response.PatchedResource = patchedResource
return response
}

View file

@ -7,8 +7,10 @@ import (
"reflect"
"strconv"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
@ -38,6 +40,65 @@ func processOverlay(rule kyverno.Rule, rawResource []byte) ([][]byte, error) {
return patches, err
}
// rawResource handles validating admission request
// Checks the target resources for rules defined in the policy
// TODO: pass in the unstructured object in stead of raw byte?
func processOverlayNew(rule kyverno.Rule, resource unstructured.Unstructured) (response RuleResponse, patchedResource unstructured.Unstructured) {
startTime := time.Now()
glog.V(4).Infof("started applying overlay rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Mutation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying overlay rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
patches, err := processOverlayPatches(resource.UnstructuredContent(), rule.Mutation.Overlay)
// resource does not satisfy the overlay pattern, we dont apply this rule
if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
glog.V(4).Infof("Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Name, rule.Mutation.Overlay)
//TODO: send zero response and not consider this as applied?
return RuleResponse{}, resource
}
if err != nil {
// rule application failed
response.Success = false
response.Message = fmt.Sprintf("failed to process overlay: %v", err)
return response, resource
}
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
response.Success = false
glog.Infof("unable to marshall resource: %v", err)
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
var patchResource []byte
patchResource, err = ApplyPatches(resourceRaw, patches)
if err != nil {
glog.Info("failed to apply patch")
response.Success = false
response.Message = fmt.Sprintf("failed to apply JSON patches: %v", err)
return response, resource
}
err = patchedResource.UnmarshalJSON(patchResource)
if err != nil {
glog.Infof("failed to unmarshall resource to undstructured: %v", err)
response.Success = false
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
// rule application succesfuly
response.Success = true
response.Message = fmt.Sprintf("succesfully process overlay")
response.Patches = patches
// apply the patches to the resource
return response, patchedResource
}
func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) {
if !meetConditions(resource, overlay) {
@ -65,6 +126,7 @@ func applyOverlay(resource, overlay interface{}, path string) ([][]byte, error)
}
appliedPatches = append(appliedPatches, patch)
//TODO : check if return is needed ?
}
return applyOverlayForSameTypes(resource, overlay, path)
}

View file

@ -3,9 +3,13 @@ package engine
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
@ -30,8 +34,8 @@ func processPatches(rule kyverno.Rule, resource []byte) (allPatches [][]byte, er
errs = append(errs, err)
continue
}
patchedDocument, err = applyPatch(patchedDocument, patchRaw)
patches := [][]byte{patchRaw}
patchedDocument, err = ApplyPatches(patchedDocument, patches)
// TODO: continue on error if one of the patches fails, will add the failure event in such case
if patch.Operation == "remove" {
glog.Info(err)
@ -84,3 +88,90 @@ func ApplyPatches(resource []byte, patches [][]byte) ([]byte, error) {
}
return patchedDocument, err
}
//ApplyPatchNew ...
func ApplyPatchNew(resource, patch []byte) ([]byte, error) {
patchesList := [][]byte{patch}
joinedPatches := JoinPatches(patchesList)
jsonpatch, err := jsonpatch.DecodePatch(joinedPatches)
if err != nil {
return nil, err
}
patchedResource, err := jsonpatch.Apply(resource)
if err != nil {
return nil, err
}
return patchedResource, err
}
func processPatchesNew(rule kyverno.Rule, resource unstructured.Unstructured) (response RuleResponse, patchedResource unstructured.Unstructured) {
startTime := time.Now()
glog.V(4).Infof("started JSON patch rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Mutation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished JSON patch rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
response.Success = false
glog.Infof("unable to marshall resource: %v", err)
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
var errs []error
var patches [][]byte
for _, patch := range rule.Mutation.Patches {
// JSON patch
patchRaw, err := json.Marshal(patch)
if err != nil {
glog.V(4).Infof("failed to marshall JSON patch %v: %v", patch, err)
errs = append(errs, err)
continue
}
patchResource, err := applyPatch(resourceRaw, patchRaw)
// TODO: continue on error if one of the patches fails, will add the failure event in such case
if err != nil && patch.Operation == "remove" {
glog.Info(err)
continue
}
if err != nil {
errs = append(errs, err)
continue
}
resourceRaw = patchResource
patches = append(patches, patchRaw)
}
// error while processing JSON patches
if len(errs) > 0 {
response.Success = false
response.Message = fmt.Sprintf("failed to process JSON patches: %v", func() string {
var str []string
for _, err := range errs {
str = append(str, err.Error())
}
return strings.Join(str, ";")
}())
return response, resource
}
err = patchedResource.UnmarshalJSON(resourceRaw)
if err != nil {
glog.Infof("failed to unmarshall resource to undstructured: %v", err)
response.Success = false
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
}
// JSON patches processed succesfully
response.Success = true
response.Message = fmt.Sprintf("succesfully process JSON patches")
response.Patches = patches
return response, patchedResource
}

116
pkg/engine/response.go Normal file
View file

@ -0,0 +1,116 @@
package engine
import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
//EngineResponseNew engine response to the action
type EngineResponseNew struct {
// Resource patched with the engine action changes
PatchedResource unstructured.Unstructured
// Policy Response
PolicyResponse PolicyResponse
}
//PolicyResponse policy application response
type PolicyResponse struct {
// policy name
Policy string `json:"policy"`
// resource details
Resource ResourceSpec `json:"resource"`
// policy statistics
PolicyStats `json:",inline"`
// rule response
Rules []RuleResponse `json:"rules"`
// ValidationFailureAction: audit,enforce(default)
ValidationFailureAction string
}
//ResourceSpec resource action applied on
type ResourceSpec struct {
//TODO: support ApiVersion
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Namespace string `json:"namespace"`
Name string `json:"name"`
}
//PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// time required to process the policy rules on a resource
ProcessingTime time.Duration `json:"processingTime"`
// Count of rules that were applied succesfully
RulesAppliedCount int `json:"rulesAppliedCount"`
}
//RuleResponse details for each rule applicatino
type RuleResponse struct {
// rule name specified in policy
Name string `json:"name"`
// rule type (Mutation,Generation,Validation) for Kyverno Policy
Type string `json:"type"`
// message response from the rule application
Message string `json:"message"`
// JSON patches, for mutation rules
Patches [][]byte `json:"patches,omitempty"`
// success/fail
Success bool `json:"success"`
// statistics
RuleStats `json:",inline"`
}
//ToString ...
func (rr RuleResponse) ToString() string {
return fmt.Sprintf("rule %s (%s): %v", rr.Name, rr.Type, rr.Message)
}
//RuleStats stores the statisctis for the single rule application
type RuleStats struct {
// time required to appliy the rule on the resource
ProcessingTime time.Duration `json:"processingTime"`
}
//IsSuccesful checks if any rule has failed or not
func (er EngineResponseNew) IsSuccesful() bool {
for _, r := range er.PolicyResponse.Rules {
if !r.Success {
return false
}
}
return true
}
//GetPatches returns all the patches joined
func (er EngineResponseNew) GetPatches() [][]byte {
var patches [][]byte
for _, r := range er.PolicyResponse.Rules {
if r.Patches != nil {
patches = append(patches, r.Patches...)
}
}
// join patches
return patches
}
//GetFailedRules returns failed rules
func (er EngineResponseNew) GetFailedRules() []string {
return er.getRules(false)
}
//GetSuccessRules returns success rules
func (er EngineResponseNew) GetSuccessRules() []string {
return er.getRules(true)
}
func (er EngineResponseNew) getRules(success bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
if r.Success == success {
rules = append(rules, r.Name)
}
}
return rules
}

View file

@ -11,7 +11,6 @@ import (
"github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -19,13 +18,35 @@ import (
"k8s.io/apimachinery/pkg/labels"
)
//EngineResponse provides the response to the application of a policy rule set on a resource
type EngineResponse struct {
Patches [][]byte
PatchedResource unstructured.Unstructured
RuleInfos []info.RuleInfo
EngineStats
}
// //EngineResponse provides the response to the application of a policy rule set on a resource
// type EngineResponse struct {
// // JSON patches for mutation rules
// Patches [][]byte
// // Resource patched with the policy changes
// PatchedResource unstructured.Unstructured
// // Rule details
// RuleInfos []info.RuleInfo
// // PolicyS
// EngineStats
// }
// type EngineResponseNew struct {
// // error while processing engine action
// Err error
// // Resource patched with the engine action changes
// PatchedResource unstructured.Unstructured
// // Policy Response
// PolicyRespone PolicyResponse
// }
// type PolicyResponse struct {
// // policy name
// Policy string
// // resource details
// Resource kyverno.ResourceSpec
// }
// type PolicyStatus
//EngineStats stores in the statistics for a single application of resource
type EngineStats struct {
@ -518,3 +539,21 @@ func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
}
return resource, nil
}
type RuleType int
const (
Mutation RuleType = iota
Validation
Generation
All
)
func (ri RuleType) String() string {
return [...]string{
"Mutation",
"Validation",
"Generation",
"All",
}[ri]
}

View file

@ -1,7 +1,6 @@
package engine
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
@ -12,102 +11,115 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// Validate handles validating admission request
// Checks the target resources for rules defined in the policy
func Validate(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponse) {
// var response EngineResponse
startTime := time.Now()
glog.V(4).Infof("started applying validation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
response.ExecutionTime = time.Since(startTime)
glog.V(4).Infof("Finished applying validation rules policy %v (%v)", policy.Name, response.ExecutionTime)
glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", response.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.RulesAppliedCount++
}
resourceRaw, err := resource.MarshalJSON()
if err != nil {
glog.V(4).Infof("Skip processing validating rule, unable to marshal resource : %v\n", err)
response.PatchedResource = resource
return response
}
// // Validate handles validating admission request
// // Checks the target resources for rules defined in the policy
// func Validate(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponse) {
// // var response EngineResponse
// startTime := time.Now()
// glog.V(4).Infof("started applying validation rules of policy %q (%v)", policy.Name, startTime)
// defer func() {
// response.ExecutionTime = time.Since(startTime)
// glog.V(4).Infof("Finished applying validation rules policy %v (%v)", policy.Name, response.ExecutionTime)
// glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", response.RulesAppliedCount, policy.Name)
// }()
// incrementAppliedRuleCount := func() {
// // rules applied succesfully count
// response.RulesAppliedCount++
// }
// resourceRaw, err := resource.MarshalJSON()
// if err != nil {
// glog.V(4).Infof("Skip processing validating rule, unable to marshal resource : %v\n", err)
// response.PatchedResource = resource
// return response
// }
var resourceInt interface{}
if err := json.Unmarshal(resourceRaw, &resourceInt); err != nil {
glog.V(4).Infof("unable to unmarshal resource : %v\n", err)
response.PatchedResource = resource
return response
}
// var resourceInt interface{}
// if err := json.Unmarshal(resourceRaw, &resourceInt); err != nil {
// glog.V(4).Infof("unable to unmarshal resource : %v\n", err)
// response.PatchedResource = resource
// return response
// }
var ruleInfos []info.RuleInfo
// var ruleInfos []info.RuleInfo
for _, rule := range policy.Spec.Rules {
if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
continue
}
// for _, rule := range policy.Spec.Rules {
// if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
// continue
// }
// check if the resource satisfies the filter conditions defined in the rule
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription(resource, rule)
if !ok {
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
// // check if the resource satisfies the filter conditions defined in the rule
// // TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// // dont statisfy a policy rule resource description
// ok := MatchesResourceDescription(resource, rule)
// if !ok {
// glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
// continue
// }
ruleInfo := validatePatterns(resource, rule.Validation, rule.Name)
incrementAppliedRuleCount()
ruleInfos = append(ruleInfos, ruleInfo)
}
response.RuleInfos = ruleInfos
return response
}
// // ruleInfo := validatePatterns(resource, rule)
// incrementAppliedRuleCount()
// // ruleInfos = append(ruleInfos, ruleInfo)
// }
// response.RuleInfos = ruleInfos
// return response
// }
// validatePatterns validate pattern and anyPattern
func validatePatterns(resource unstructured.Unstructured, validation kyverno.Validation, ruleName string) info.RuleInfo {
var errs []error
ruleInfo := info.NewRuleInfo(ruleName, info.Validation)
func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (response RuleResponse) {
startTime := time.Now()
glog.V(4).Infof("started applying validation rule %q (%v)", rule.Name, startTime)
response.Name = rule.Name
response.Type = Validation.String()
defer func() {
response.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying validation rule %q (%v)", response.Name, response.RuleStats.ProcessingTime)
}()
if validation.Pattern != nil {
err := validateResourceWithPattern(resource.Object, validation.Pattern)
// either pattern or anyPattern can be specified in Validation rule
if rule.Validation.Pattern != nil {
err := validateResourceWithPattern(resource.Object, rule.Validation.Pattern)
if err != nil {
ruleInfo.Fail()
ruleInfo.Addf("Failed to apply pattern: %v", err)
} else {
ruleInfo.Add("Pattern succesfully validated")
glog.V(4).Infof("pattern validated succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
// rule application failed
glog.V(4).Infof("failed to apply validation for rule %s on resource %s/%s/%s, pattern %v ", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Pattern)
response.Success = false
response.Message = fmt.Sprintf("failed to apply pattern: %v", err)
return response
}
return ruleInfo
// rule application succesful
glog.V(4).Infof("rule %s pattern validated succesfully on resource %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
response.Success = true
response.Message = fmt.Sprintf("validation pattern succesfully validated")
return response
}
if validation.AnyPattern != nil {
for _, pattern := range validation.AnyPattern {
//TODO: add comments to explain the flow
if rule.Validation.AnyPattern != nil {
var errs []error
for _, pattern := range rule.Validation.AnyPattern {
if err := validateResourceWithPattern(resource.Object, pattern); err != nil {
errs = append(errs, err)
}
}
failedPattern := len(errs)
patterns := len(validation.AnyPattern)
// all pattern fail
if failedPattern == patterns {
ruleInfo.Fail()
ruleInfo.Addf("None of anyPattern succeed: %v", errs)
} else {
ruleInfo.Addf("%d/%d patterns succesfully validated", patterns-failedPattern, patterns)
failedPattern := len(errs)
patterns := len(rule.Validation.AnyPattern)
// all patterns fail
if failedPattern == patterns {
// any Pattern application failed
glog.V(4).Infof("none of anyPattern were processed: %v", errs)
response.Success = false
response.Message = fmt.Sprintf("None of anyPattern succeed: %v", errs)
return response
}
// any Pattern application succesful
glog.V(4).Infof("%d/%d patterns validated succesfully on resource %s/%s", patterns-failedPattern, patterns, resource.GetNamespace(), resource.GetName())
response.Success = true
response.Message = fmt.Sprintf("%d/%d patterns succesfully validated", patterns-failedPattern, patterns)
return response
}
return ruleInfo
}
return info.RuleInfo{}
return RuleResponse{}
}
// validateResourceWithPattern is a start of element-by-element validation process
@ -328,3 +340,51 @@ func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]i
handler := CreateAnchorHandler(anchor, pattern, path)
return handler.Handle(resourceMapArray, patternMap, originPattern)
}
//ValidateNew ...
func ValidateNew(policy kyverno.Policy, resource unstructured.Unstructured) (response EngineResponseNew) {
startTime := time.Now()
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = resource.GetName()
response.PolicyResponse.Resource.Namespace = resource.GetNamespace()
response.PolicyResponse.Resource.Kind = resource.GetKind()
response.PolicyResponse.Resource.APIVersion = resource.GetAPIVersion()
response.PolicyResponse.ValidationFailureAction = policy.Spec.ValidationFailureAction
}()
glog.V(4).Infof("started applying validation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
response.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("Finished applying validation rules policy %v (%v)", policy.Name, response.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", response.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.PolicyResponse.RulesAppliedCount++
}
for _, rule := range policy.Spec.Rules {
if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription(resource, rule)
if !ok {
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
if rule.Validation.Pattern != nil || rule.Validation.AnyPattern != nil {
ruleResponse := validatePatterns(resource, rule)
incrementAppliedRuleCount()
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
}
}
return response
}

View file

@ -1571,8 +1571,8 @@ func TestValidate_ServiceTest(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
res := Validate(policy, *resourceUnstructured)
assert.Assert(t, len(res.RuleInfos) == 0)
er := ValidateNew(policy, *resourceUnstructured)
assert.Assert(t, len(er.PolicyResponse.Rules) == 0)
}
func TestValidate_MapHasFloats(t *testing.T) {
@ -1668,6 +1668,6 @@ func TestValidate_MapHasFloats(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
res := Validate(policy, *resourceUnstructured)
assert.Assert(t, len(res.RuleInfos) == 0)
er := ValidateNew(policy, *resourceUnstructured)
assert.Assert(t, len(er.PolicyResponse.Rules) == 0)
}

View file

@ -28,7 +28,7 @@ type Generator struct {
//Interface to generate event
type Interface interface {
Add(infoList ...*Info)
Add(infoList ...Info)
}
//NewEventGenerator to generate a new event controller
@ -69,9 +69,9 @@ func initRecorder(client *client.Client) record.EventRecorder {
}
//Add queues an event for generation
func (gen *Generator) Add(infos ...*Info) {
func (gen *Generator) Add(infos ...Info) {
for _, info := range infos {
gen.queue.Add(*info)
gen.queue.Add(info)
}
}
@ -180,3 +180,24 @@ func NewEvent(rkind string, rnamespace string, rname string, reason Reason, mess
Message: msgText,
}
}
func NewEventNew(
rkind,
rapiVersion,
rnamespace,
rname,
reason string,
message MsgKey,
args ...interface{}) Info {
msgText, err := getEventMsg(message, args...)
if err != nil {
glog.Error(err)
}
return Info{
Kind: rkind,
Name: rname,
Namespace: rnamespace,
Reason: reason,
Message: msgText,
}
}

View file

@ -1,192 +0,0 @@
package info
import (
"fmt"
"strings"
)
//PolicyInfo defines policy information
type PolicyInfo struct {
// Name is policy name
Name string
// RKind represents the resource kind
RKind string
// RName is resource name
RName string
// Namespace is the ns of resource
// empty on non-namespaced resources
RNamespace string
//TODO: add check/enum for types
ValidationFailureAction string // BlockChanges, ReportViolation
Rules []RuleInfo
success bool
}
//NewPolicyInfo returns a new policy info
func NewPolicyInfo(policyName, rKind, rName, rNamespace, validationFailureAction string) PolicyInfo {
pi := PolicyInfo{
Name: policyName,
RKind: rKind,
RName: rName,
RNamespace: rNamespace,
success: true, // fail to be set explicity
ValidationFailureAction: validationFailureAction,
}
return pi
}
//IsSuccessful checks if policy is succesful
// the policy is set to fail, if any of the rules have failed
func (pi *PolicyInfo) IsSuccessful() bool {
for _, r := range pi.Rules {
if !r.success {
pi.success = false
return false
}
}
pi.success = true
return true
}
// SuccessfulRules returns list of successful rule names
func (pi *PolicyInfo) SuccessfulRules() []string {
var rules []string
for _, r := range pi.Rules {
if r.IsSuccessful() {
rules = append(rules, r.Name)
}
}
return rules
}
// FailedRules returns list of failed rule names
func (pi *PolicyInfo) FailedRules() []string {
var rules []string
for _, r := range pi.Rules {
if !r.IsSuccessful() {
rules = append(rules, r.Name)
}
}
return rules
}
//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.ToString())
}
}
return strings.Join(errorMsgs, ";")
}
type RuleType int
const (
Mutation RuleType = iota
Validation
Generation
All
)
func (ri RuleType) String() string {
return [...]string{
"Mutation",
"Validation",
"Generation",
"All",
}[ri]
}
//RuleInfo defines rule struct
type RuleInfo struct {
Name string
RuleType RuleType
Msgs []string
Patches [][]byte // this will store the mutation patch being applied by the rule
success bool
}
//ToString reule information
//TODO: check if this is needed
func (ri *RuleInfo) ToString() string {
str := "rulename: " + ri.Name
msgs := strings.Join(ri.Msgs, ";")
return strings.Join([]string{str, msgs}, ";")
}
//GetErrorString returns the error message for a rule
func (ri *RuleInfo) GetErrorString() string {
return strings.Join(ri.Msgs, ";")
}
//NewRuleInfo creates a new RuleInfo
func NewRuleInfo(ruleName string, ruleType RuleType) RuleInfo {
return RuleInfo{
Name: ruleName,
Msgs: []string{},
RuleType: ruleType,
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 rules == nil {
return
}
if !rulesSuccesfuly(rules) {
pi.success = false
}
pi.Rules = append(pi.Rules, rules...)
}
//GetRuleNames gets the name of successful rules
func (pi *PolicyInfo) GetRuleNames(onSuccess bool) string {
var ruleNames []string
for _, rule := range pi.Rules {
if onSuccess {
if rule.IsSuccessful() {
ruleNames = append(ruleNames, rule.Name)
}
} else {
if !rule.IsSuccessful() {
ruleNames = append(ruleNames, rule.Name)
}
}
}
return strings.Join(ruleNames, ",")
}

View file

@ -10,7 +10,6 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -100,42 +99,31 @@ func applyPolicyOnRaw(policy *kyverno.Policy, rawResource []byte, gvk *metav1.Gr
rname := engine.ParseNameFromObject(rawResource)
rns := engine.ParseNamespaceFromObject(rawResource)
policyInfo := info.NewPolicyInfo(policy.Name,
gvk.Kind,
rname,
rns,
policy.Spec.ValidationFailureAction)
resource, err := ConvertToUnstructured(rawResource)
if err != nil {
return nil, err
}
//TODO check if the kind information is present resource
// Process Mutation
engineResponse := engine.Mutate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if !policyInfo.IsSuccessful() {
engineResponse := engine.MutateNew(*policy, *resource)
if !engineResponse.IsSuccesful() {
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
for _, r := range engineResponse.RuleInfos {
glog.Warning(r.Msgs)
for _, r := range engineResponse.PolicyResponse.Rules {
glog.Warning(r.Message)
}
} else if len(engineResponse.Patches) > 0 {
} else if len(engineResponse.PolicyResponse.Rules) > 0 {
glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns)
patchedResource, err = engine.ApplyPatches(rawResource, engineResponse.Patches)
if err != nil {
return nil, fmt.Errorf("Unable to apply mutation patches:\n%v", err)
}
// Process Validation
engineResponse := engine.Validate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if !policyInfo.IsSuccessful() {
// Process Validation
engineResponse := engine.ValidateNew(*policy, *resource)
if !engineResponse.IsSuccesful() {
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
for _, r := range engineResponse.RuleInfos {
glog.Warning(r.Msgs)
for _, r := range engineResponse.PolicyResponse.Rules {
glog.Warning(r.Message)
}
return patchedResource, fmt.Errorf("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
} else if len(engineResponse.RuleInfos) > 0 {
} else if len(engineResponse.PolicyResponse.Rules) > 0 {
glog.Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns)
}
}

View file

@ -202,8 +202,8 @@ func (nsc *NamespaceController) syncNamespace(key string) error {
n := namespace.DeepCopy()
// process generate rules
policyInfos := nsc.processNamespace(*n)
engineResponses := nsc.processNamespace(*n)
// report errors
nsc.report(policyInfos)
nsc.report(engineResponses)
return nil
}

View file

@ -9,8 +9,8 @@ import (
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/policy"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/utils"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
@ -85,13 +85,12 @@ func buildKey(policy, pv, kind, ns, name, rv string) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}
func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []info.PolicyInfo {
var policyInfos []info.PolicyInfo
func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []engine.EngineResponseNew {
// convert to unstructured
unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&namespace)
if err != nil {
glog.Infof("unable to convert to unstructured, not processing any policies: %v", err)
return policyInfos
return nil
}
nsc.rm.Drop()
@ -100,18 +99,20 @@ func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []i
// get all the policies that have a generate rule and resource description satifies the namespace
// apply policy on resource
policies := listpolicies(ns, nsc.pLister)
var engineResponses []engine.EngineResponseNew
for _, policy := range policies {
// pre-processing, check if the policy and resource version has been processed before
if !nsc.rm.ProcessResource(policy.Name, policy.ResourceVersion, ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion()) {
glog.V(4).Infof("policy %s with resource version %s already processed on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion())
continue
}
policyInfo := applyPolicy(nsc.client, ns, *policy, nsc.policyStatus)
policyInfos = append(policyInfos, policyInfo)
engineResponse := applyPolicy(nsc.client, ns, *policy, nsc.policyStatus)
engineResponses = append(engineResponses, engineResponse)
// post-processing, register the resource as processed
nsc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion())
}
return policyInfos
return engineResponses
}
func listpolicies(ns unstructured.Unstructured, pLister kyvernolister.PolicyLister) []*kyverno.Policy {
@ -139,17 +140,23 @@ func listpolicies(ns unstructured.Unstructured, pLister kyvernolister.PolicyList
return filteredpolicies
}
func applyPolicy(client *client.Client, resource unstructured.Unstructured, p kyverno.Policy, policyStatus policy.PolicyStatusInterface) info.PolicyInfo {
var ps policy.PolicyStat
gatherStat := func(policyName string, er engine.EngineResponse) {
func applyPolicy(client *client.Client, resource unstructured.Unstructured, p kyverno.Policy, policyStatus policyctr.PolicyStatusInterface) engine.EngineResponseNew {
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.GenerationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
policyStats = append(policyStats, ps)
}
// send stats for aggregation
sendStat := func(blocked bool) {
//SEND
policyStatus.SendStat(ps)
for _, stat := range policyStats {
stat.Stats.ResourceBlocked = utils.Btoi(blocked)
//SEND
policyStatus.SendStat(stat)
}
}
startTime := time.Now()
@ -157,13 +164,11 @@ func applyPolicy(client *client.Client, resource unstructured.Unstructured, p ky
defer func() {
glog.V(4).Infof("Finished applying %s on resource %s/%s/%s (%v)", p.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), time.Since(startTime))
}()
policyInfo := info.NewPolicyInfo(p.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), p.Spec.ValidationFailureAction)
engineResponse := engine.Generate(client, p, resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// gather stats
gatherStat(p.Name, engineResponse)
gatherStat(p.Name, engineResponse.PolicyResponse)
//send stats
sendStat(false)
return policyInfo
return engineResponse
}

View file

@ -4,56 +4,54 @@ import (
"fmt"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/policyviolation"
)
func (nsc *NamespaceController) report(policyInfos []info.PolicyInfo) {
func (nsc *NamespaceController) report(engineResponses []engine.EngineResponseNew) {
// generate events
// generate policy violations
for _, policyInfo := range policyInfos {
for _, er := range engineResponses {
// events
// success - policy applied on resource
// failure - policy/rule failed to apply on the resource
reportEvents(policyInfo, nsc.eventGen)
reportEvents(er, nsc.eventGen)
// policy violations
// failure - policy/rule failed to apply on the resource
}
// generate policy violation
policyviolation.GeneratePolicyViolations(nsc.pvListerSynced, nsc.pvLister, nsc.kyvernoClient, policyInfos)
policyviolation.CreatePV(nsc.pvLister, nsc.kyvernoClient, engineResponses)
}
//reportEvents generates events for the failed resources
func reportEvents(policyInfo info.PolicyInfo, eventGen event.Interface) {
if policyInfo.IsSuccessful() {
func reportEvents(engineResponse engine.EngineResponseNew, eventGen event.Interface) {
if engineResponse.IsSuccesful() {
return
}
glog.V(4).Infof("reporting results for policy %s application on resource %s/%s/%s", policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName)
for _, rule := range policyInfo.Rules {
if rule.IsSuccessful() {
continue
glog.V(4).Infof("reporting results for policy %s application on resource %s/%s/%s", engineResponse.PolicyResponse.Policy, engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name)
for _, rule := range engineResponse.PolicyResponse.Rules {
if rule.Success {
return
}
// generate event on resource for each failed rule
e := &event.Info{}
e.Kind = policyInfo.RKind
e.Namespace = policyInfo.RNamespace
e.Name = policyInfo.RName
glog.V(4).Infof("generation event on resource %s/%s/%s for policy %s", engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name, engineResponse.PolicyResponse.Policy)
e := event.Info{}
e.Kind = engineResponse.PolicyResponse.Resource.Kind
e.Namespace = engineResponse.PolicyResponse.Resource.Namespace
e.Name = engineResponse.PolicyResponse.Policy
e.Reason = "Failure"
e.Message = fmt.Sprintf("policy %s (%s) rule %s failed to apply. %v", policyInfo.Name, rule.RuleType.String(), rule.Name, rule.GetErrorString())
e.Message = fmt.Sprintf("policy %s (%s) rule %s failed to apply. %v", engineResponse.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message)
eventGen.Add(e)
}
// generate a event on policy for all failed rules
e := &event.Info{}
glog.V(4).Infof("generation event on policy %s", engineResponse.PolicyResponse.Policy)
e := event.Info{}
e.Kind = "Policy"
e.Namespace = ""
e.Name = policyInfo.Name
e.Name = engineResponse.PolicyResponse.Policy
e.Reason = "Failure"
e.Message = fmt.Sprintf("failed to apply rules %s on resource %s/%s/%s", policyInfo.FailedRules(), policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName)
e.Message = fmt.Sprintf("failed to apply rules %v on resource %s/%s/%s", engineResponse.GetFailedRules(), engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name)
eventGen.Add(e)
}

View file

@ -1,131 +1,117 @@
package policy
import (
"fmt"
"reflect"
"time"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// applyPolicy applies policy on a resource
//TODO: generation rules
func applyPolicy(policy kyverno.Policy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (info.PolicyInfo, error) {
var ps PolicyStat
gatherStat := func(policyName string, er engine.EngineResponse) {
// ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.ValidationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
}
// send stats for aggregation
sendStat := func(blocked bool) {
//SEND
policyStatus.SendStat(ps)
}
func applyPolicy(policy kyverno.Policy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (responses []engine.EngineResponseNew) {
startTime := time.Now()
var policyStats []PolicyStat
glog.V(4).Infof("Started apply policy %s on resource %s/%s/%s (%v)", policy.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), startTime)
defer func() {
glog.V(4).Infof("Finished applying %s on resource %s/%s/%s (%v)", policy.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), time.Since(startTime))
}()
// glog.V(4).Infof("apply policy %s with resource version %s on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
// gather stats from the engine response
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
ps := PolicyStat{}
ps.PolicyName = policyName
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
policyStats = append(policyStats, ps)
}
// send stats for aggregation
sendStat := func(blocked bool) {
for _, stat := range policyStats {
stat.Stats.ResourceBlocked = utils.Btoi(blocked)
//SEND
policyStatus.SendStat(stat)
}
}
var engineResponses []engine.EngineResponseNew
var engineResponse engine.EngineResponseNew
var err error
//MUTATION
mruleInfos, err := mutation(policy, resource, policyStatus)
policyInfo.AddRuleInfos(mruleInfos)
engineResponse, err = mutation(policy, resource, policyStatus)
engineResponses = append(engineResponses, engineResponse)
if err != nil {
return policyInfo, err
glog.Errorf("unable to process mutation rules: %v", err)
}
gatherStat(policy.Name, engineResponse.PolicyResponse)
//send stats
sendStat(false)
//VALIDATION
engineResponse := engine.Validate(policy, resource)
if len(engineResponse.RuleInfos) != 0 {
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
}
engineResponse = engine.ValidateNew(policy, resource)
engineResponses = append(engineResponses, engineResponse)
// gather stats
gatherStat(policy.Name, engineResponse)
gatherStat(policy.Name, engineResponse.PolicyResponse)
//send stats
sendStat(false)
//TODO: GENERATION
return policyInfo, nil
return engineResponses
}
func mutation(policy kyverno.Policy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (engine.EngineResponseNew, error) {
engineResponse := engine.MutateNew(policy, resource)
if !engineResponse.IsSuccesful() {
glog.V(4).Infof("mutation had errors reporting them")
return engineResponse, nil
}
// Verify if the JSON pathes returned by the Mutate are already applied to the resource
if reflect.DeepEqual(resource, engineResponse.PatchedResource) {
// resources matches
glog.V(4).Infof("resource %s/%s/%s satisfies policy %s", engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name, engineResponse.PolicyResponse.Policy)
return engineResponse, nil
}
return getFailedOverallRuleInfo(resource, engineResponse)
}
func mutation(policy kyverno.Policy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) ([]info.RuleInfo, error) {
var ps PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, er engine.EngineResponse) {
// ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.MutationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
}
// send stats for aggregation
sendStat := func(blocked bool) {
//SEND
policyStatus.SendStat(ps)
}
engineResponse := engine.Mutate(policy, resource)
// gather stats
gatherStat(policy.Name, engineResponse)
//send stats
sendStat(false)
patches := engineResponse.Patches
ruleInfos := engineResponse.RuleInfos
if len(ruleInfos) == 0 {
//no rules processed
return nil, nil
}
for _, r := range ruleInfos {
if !r.IsSuccessful() {
// no failures while processing rule
return ruleInfos, nil
}
}
if len(patches) == 0 {
// no patches for the resources
// either there were failures or the overlay already was satisfied
return ruleInfos, nil
}
// (original resource + patch) == (original resource)
mergePatches := utils.JoinPatches(patches)
patch, err := jsonpatch.DecodePatch(mergePatches)
if err != nil {
return nil, err
}
// getFailedOverallRuleInfo gets detailed info for over-all mutation failure
func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse engine.EngineResponseNew) (engine.EngineResponseNew, error) {
rawResource, err := resource.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
return nil, err
glog.V(4).Infof("unable to marshal resource: %v\n", err)
return engine.EngineResponseNew{}, err
}
// apply the patches returned by mutate to the original resource
patchedResource, err := patch.Apply(rawResource)
if err != nil {
return nil, err
}
//TODO: this will be removed after the support for patching for each rule
ruleInfo := info.NewRuleInfo("over-all mutation", info.Mutation)
// resource does not match so there was a mutation rule violated
for index, rule := range engineResponse.PolicyResponse.Rules {
glog.V(4).Infof("veriying if policy %s rule %s was applied before to resource %s/%s/%s", engineResponse.PolicyResponse.Policy, rule.Name, engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name)
if len(rule.Patches) == 0 {
continue
}
if !jsonpatch.Equal(patchedResource, rawResource) {
//resource does not match so there was a mutation rule violated
// TODO : check the rule name "mutation rules"
ruleInfo.Fail()
ruleInfo.Add("resource does not satisfy mutation rules")
} else {
ruleInfo.Add("resource satisfys the mutation rule")
}
patch, err := jsonpatch.DecodePatch(utils.JoinPatches(rule.Patches))
if err != nil {
glog.V(4).Infof("unable to decode patch %s: %v", rule.Patches, err)
return engine.EngineResponseNew{}, err
}
ruleInfos = append(ruleInfos, ruleInfo)
return ruleInfos, nil
// apply the patches returned by mutate to the original resource
patchedResource, err := patch.Apply(rawResource)
if err != nil {
glog.V(4).Infof("unable to apply patch %s: %v", rule.Patches, err)
return engine.EngineResponseNew{}, err
}
if !jsonpatch.Equal(patchedResource, rawResource) {
glog.V(4).Infof("policy %s rule %s condition not satisifed by existing resource", engineResponse.PolicyResponse.Policy, rule.Name)
engineResponse.PolicyResponse.Rules[index].Success = false
engineResponse.PolicyResponse.Rules[index].Message = fmt.Sprintf("rule not satisfied by existing resource.")
}
}
return engineResponse, nil
}

View file

@ -8,17 +8,17 @@ import (
"github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func (pc *PolicyController) processExistingResources(policy kyverno.Policy) []info.PolicyInfo {
func (pc *PolicyController) processExistingResources(policy kyverno.Policy) []engine.EngineResponseNew {
// Parse through all the resources
// drops the cache after configured rebuild time
pc.rm.Drop()
var policyInfos []info.PolicyInfo
var engineResponses []engine.EngineResponseNew
// get resource that are satisfy the resource description defined in the rules
resourceMap := listResources(pc.client, policy, pc.filterK8Resources)
for _, resource := range resourceMap {
@ -29,21 +29,13 @@ func (pc *PolicyController) processExistingResources(policy kyverno.Policy) []in
}
// apply the policy on each
glog.V(4).Infof("apply policy %s with resource version %s on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
policyInfo := applyPolicyOnResource(policy, resource, pc.statusAggregator)
policyInfos = append(policyInfos, *policyInfo)
engineResponse := applyPolicy(policy, resource, pc.statusAggregator)
// get engine response for mutation & validation indipendently
engineResponses = append(engineResponses, engineResponse...)
// post-processing, register the resource as processed
pc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
}
return policyInfos
}
func applyPolicyOnResource(policy kyverno.Policy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) *info.PolicyInfo {
policyInfo, err := applyPolicy(policy, resource, policyStatus)
if err != nil {
glog.V(4).Infof("failed to process policy %s on resource %s/%s/%s: %v", policy.GetName(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), err)
return nil
}
return &policyInfo
return engineResponses
}
func listResources(client *client.Client, policy kyverno.Policy, filterK8Resources []utils.K8Resource) map[string]unstructured.Unstructured {

View file

@ -4,15 +4,15 @@ import (
"fmt"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/policyviolation"
)
func (pc *PolicyController) report(policyInfos []info.PolicyInfo) {
func (pc *PolicyController) report(engineResponses []engine.EngineResponseNew) {
// generate events
// generate policy violations
for _, policyInfo := range policyInfos {
for _, policyInfo := range engineResponses {
// events
// success - policy applied on resource
// failure - policy/rule failed to apply on the resource
@ -22,37 +22,39 @@ func (pc *PolicyController) report(policyInfos []info.PolicyInfo) {
}
// generate policy violation
policyviolation.GeneratePolicyViolations(pc.pvListerSynced, pc.pvLister, pc.kyvernoClient, policyInfos)
policyviolation.CreatePV(pc.pvLister, pc.kyvernoClient, engineResponses)
}
//reportEvents generates events for the failed resources
func reportEvents(policyInfo info.PolicyInfo, eventGen event.Interface) {
if policyInfo.IsSuccessful() {
func reportEvents(engineResponse engine.EngineResponseNew, eventGen event.Interface) {
if engineResponse.IsSuccesful() {
return
}
glog.V(4).Infof("reporting results for policy %s application on resource %s/%s/%s", policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName)
for _, rule := range policyInfo.Rules {
if rule.IsSuccessful() {
continue
glog.V(4).Infof("reporting results for policy %s application on resource %s/%s/%s", engineResponse.PolicyResponse.Policy, engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name)
for _, rule := range engineResponse.PolicyResponse.Rules {
if rule.Success {
return
}
// generate event on resource for each failed rule
e := &event.Info{}
e.Kind = policyInfo.RKind
e.Namespace = policyInfo.RNamespace
e.Name = policyInfo.RName
glog.V(4).Infof("generation event on resource %s/%s/%s for policy %s", engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name, engineResponse.PolicyResponse.Policy)
e := event.Info{}
e.Kind = engineResponse.PolicyResponse.Resource.Kind
e.Namespace = engineResponse.PolicyResponse.Resource.Namespace
e.Name = engineResponse.PolicyResponse.Policy
e.Reason = "Failure"
e.Message = fmt.Sprintf("policy %s (%s) rule %s failed to apply. %v", policyInfo.Name, rule.RuleType.String(), rule.Name, rule.GetErrorString())
e.Message = fmt.Sprintf("policy %s (%s) rule %s failed to apply. %v", engineResponse.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message)
eventGen.Add(e)
}
// generate a event on policy for all failed rules
e := &event.Info{}
glog.V(4).Infof("generation event on policy %s", engineResponse.PolicyResponse.Policy)
e := event.Info{}
e.Kind = "Policy"
e.Namespace = ""
e.Name = policyInfo.Name
e.Name = engineResponse.PolicyResponse.Policy
e.Reason = "Failure"
e.Message = fmt.Sprintf("failed to apply rules %s on resource %s/%s/%s", policyInfo.FailedRules(), policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName)
e.Message = fmt.Sprintf("failed to apply rules %v on resource %s/%s/%s", engineResponse.GetFailedRules(), engineResponse.PolicyResponse.Resource.Kind, engineResponse.PolicyResponse.Resource.Namespace, engineResponse.PolicyResponse.Resource.Name)
eventGen.Add(e)
}

17
pkg/policy/utils.go Normal file
View file

@ -0,0 +1,17 @@
package policy
import kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
// reEvaulatePolicy checks if the policy needs to be re-evaulated
// during re-evaulation we remove all the old policy violations and re-create new ones
// - Rule count changes
// - Rule resource description changes
// - Rule operation changes
// - Rule name changed
func reEvaulatePolicy(curP, oldP *kyverno.Policy) bool {
// count of rules changed
if len(curP.Spec.Rules) != len(curP.Spec.Rules) {
}
return true
}

View file

@ -8,7 +8,7 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/engine"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
)
@ -29,79 +29,153 @@ func BuildPolicyViolation(policy string, resource kyverno.ResourceSpec, fRules [
}
// buildPolicyViolationsForAPolicy returns a policy violation object if there are any rules that fail
func buildPolicyViolationsForAPolicy(pi info.PolicyInfo) kyverno.PolicyViolation {
var fRules []kyverno.ViolatedRule
var pv kyverno.PolicyViolation
for _, r := range pi.Rules {
if !r.IsSuccessful() {
fRules = append(fRules, kyverno.ViolatedRule{Name: r.Name, Message: r.GetErrorString(), Type: r.RuleType.String()})
// func buildPolicyViolationsForAPolicy(pi info.PolicyInfo) kyverno.PolicyViolation {
// var fRules []kyverno.ViolatedRule
// var pv kyverno.PolicyViolation
// for _, r := range pi.Rules {
// if !r.IsSuccessful() {
// fRules = append(fRules, kyverno.ViolatedRule{Name: r.Name, Message: r.GetErrorString(), Type: r.RuleType.String()})
// }
// }
// if len(fRules) > 0 {
// glog.V(4).Infof("building policy violation for policy %s on resource %s/%s/%s", pi.Name, pi.RKind, pi.RNamespace, pi.RName)
// // there is an error
// pv = BuildPolicyViolation(pi.Name, kyverno.ResourceSpec{
// Kind: pi.RKind,
// Namespace: pi.RNamespace,
// Name: pi.RName,
// },
// fRules,
// )
// }
// return pv
// }
func buildPVForPolicy(er engine.EngineResponseNew) kyverno.PolicyViolation {
var violatedRules []kyverno.ViolatedRule
glog.V(4).Infof("building policy violation for engine response %v", er)
for _, r := range er.PolicyResponse.Rules {
// filter failed/violated rules
if !r.Success {
vrule := kyverno.ViolatedRule{
Name: r.Name,
Message: r.Message,
Type: r.Type,
}
violatedRules = append(violatedRules, vrule)
}
}
if len(fRules) > 0 {
glog.V(4).Infof("building policy violation for policy %s on resource %s/%s/%s", pi.Name, pi.RKind, pi.RNamespace, pi.RName)
// there is an error
pv = BuildPolicyViolation(pi.Name, kyverno.ResourceSpec{
Kind: pi.RKind,
Namespace: pi.RNamespace,
Name: pi.RName,
pv := BuildPolicyViolation(er.PolicyResponse.Policy,
kyverno.ResourceSpec{
Kind: er.PolicyResponse.Resource.Kind,
Namespace: er.PolicyResponse.Resource.Namespace,
Name: er.PolicyResponse.Resource.Name,
},
fRules,
)
}
violatedRules,
)
return pv
}
//generatePolicyViolations generate policyViolation resources for the rules that failed
//TODO: check if pvListerSynced is needed
func GeneratePolicyViolations(pvListerSynced cache.InformerSynced, pvLister kyvernolister.PolicyViolationLister, client *kyvernoclient.Clientset, policyInfos []info.PolicyInfo) {
//CreatePV creates policy violation resource based on the engine responses
func CreatePV(pvLister kyvernolister.PolicyViolationLister, client *kyvernoclient.Clientset, engineResponses []engine.EngineResponseNew) {
var pvs []kyverno.PolicyViolation
for _, policyInfo := range policyInfos {
if !policyInfo.IsSuccessful() {
if pv := buildPolicyViolationsForAPolicy(policyInfo); !reflect.DeepEqual(pv, kyverno.PolicyViolation{}) {
for _, er := range engineResponses {
if !er.IsSuccesful() {
if pv := buildPVForPolicy(er); !reflect.DeepEqual(pv, kyverno.PolicyViolation{}) {
pvs = append(pvs, pv)
}
}
}
if len(pvs) > 0 {
for _, newPv := range pvs {
// generate PolicyViolation objects
glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name)
// check if there was a previous violation for policy & resource combination
curPv, err := getExistingPolicyViolationIfAny(pvListerSynced, pvLister, newPv)
if err != nil {
continue
}
if curPv == nil {
// no existing policy violation, create a new one
_, err := client.KyvernoV1alpha1().PolicyViolations().Create(&newPv)
if err != nil {
glog.Error(err)
}
continue
}
// compare the policyviolation spec for existing resource if present else
if reflect.DeepEqual(curPv.Spec, newPv.Spec) {
// if they are equal there has been no change so dont update the polivy violation
glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec)
continue
}
// spec changed so update the policyviolation
//TODO: wont work, as name is not defined yet
_, err = client.KyvernoV1alpha1().PolicyViolations().Update(&newPv)
if len(pvs) == 0 {
return
}
for _, newPv := range pvs {
glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name)
// check if there was a previous policy voilation for policy & resource combination
curPv, err := getExistingPolicyViolationIfAny(nil, pvLister, newPv)
if err != nil {
glog.Error(err)
continue
}
if curPv == nil {
glog.V(4).Infof("creating new policy violation for policy %s & resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.ResourceSpec.Kind, newPv.Spec.ResourceSpec.Namespace, newPv.Spec.ResourceSpec.Name)
// no existing policy violation, create a new one
_, err := client.KyvernoV1alpha1().PolicyViolations().Create(&newPv)
if err != nil {
glog.Error(err)
continue
}
continue
}
// compare the policyviolation spec for existing resource if present else
if reflect.DeepEqual(curPv.Spec, newPv.Spec) {
// if they are equal there has been no change so dont update the polivy violation
glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec)
continue
}
// spec changed so update the policyviolation
glog.V(4).Infof("creating new policy violation for policy %s & resource %s/%s/%s", curPv.Spec.Policy, curPv.Spec.ResourceSpec.Kind, curPv.Spec.ResourceSpec.Namespace, curPv.Spec.ResourceSpec.Name)
//TODO: using a generic name, but would it be helpful to have naming convention for policy violations
// as we can only have one policy violation for each (policy + resource) combination
_, err = client.KyvernoV1alpha1().PolicyViolations().Update(&newPv)
if err != nil {
glog.Error(err)
continue
}
}
}
// //GeneratePolicyViolations generate policyViolation resources for the rules that failed
// //TODO: check if pvListerSynced is needed
// func GeneratePolicyViolations(pvListerSynced cache.InformerSynced, pvLister kyvernolister.PolicyViolationLister, client *kyvernoclient.Clientset, policyInfos []info.PolicyInfo) {
// var pvs []kyverno.PolicyViolation
// for _, policyInfo := range policyInfos {
// if !policyInfo.IsSuccessful() {
// if pv := buildPolicyViolationsForAPolicy(policyInfo); !reflect.DeepEqual(pv, kyverno.PolicyViolation{}) {
// pvs = append(pvs, pv)
// }
// }
// }
// if len(pvs) > 0 {
// for _, newPv := range pvs {
// // generate PolicyViolation objects
// glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name)
// // check if there was a previous violation for policy & resource combination
// curPv, err := getExistingPolicyViolationIfAny(pvListerSynced, pvLister, newPv)
// if err != nil {
// continue
// }
// if curPv == nil {
// // no existing policy violation, create a new one
// _, err := client.KyvernoV1alpha1().PolicyViolations().Create(&newPv)
// if err != nil {
// glog.Error(err)
// }
// continue
// }
// // compare the policyviolation spec for existing resource if present else
// if reflect.DeepEqual(curPv.Spec, newPv.Spec) {
// // if they are equal there has been no change so dont update the polivy violation
// glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec)
// continue
// }
// // spec changed so update the policyviolation
// //TODO: wont work, as name is not defined yet
// _, err = client.KyvernoV1alpha1().PolicyViolations().Update(&newPv)
// if err != nil {
// glog.Error(err)
// continue
// }
// }
// }
// }
//TODO: change the name
func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvLister kyvernolister.PolicyViolationLister, newPv kyverno.PolicyViolation) (*kyverno.PolicyViolation, error) {
// TODO: check for existing ov using label selectors on resource and policy
// TODO: there can be duplicates, as the labels have not been assigned to the policy violation yet
labelMap := map[string]string{"policy": newPv.Spec.Policy, "resource": newPv.Spec.ResourceSpec.ToKey()}
ls := &metav1.LabelSelector{}
err := metav1.Convert_Map_string_To_string_To_v1_LabelSelector(&labelMap, ls, nil)

418
pkg/testrunner/scenario.go Normal file
View file

@ -0,0 +1,418 @@
package testrunner
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
ospath "path"
"path/filepath"
"reflect"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
apiyaml "k8s.io/apimachinery/pkg/util/yaml"
)
type scenarioT struct {
testCases []scaseT
}
//scase defines input and output for a case
type scaseT struct {
Input sInput `yaml:"input"`
Expected sExpected `yaml:"expected"`
}
//sInput defines input for a test scenario
type sInput struct {
Policy string `yaml:"policy"`
Resource string `yaml:"resource"`
LoadResources []string `yaml:"loadresources,omitempty"`
}
type sExpected struct {
Mutation sMutation `yaml:"mutation,omitempty"`
Validation sValidation `yaml:"validation,omitempty"`
// Generation sGeneration `yaml:"generation,omitempty"`
}
type sMutation struct {
// path to the patched resource to be compared with
PatchedResource string `yaml:"patchedresource,omitempty"`
// expected respone from the policy engine
PolicyResponse engine.PolicyResponse `yaml:"policyresponse"`
}
type sValidation struct {
// expected respone from the policy engine
PolicyResponse engine.PolicyResponse `yaml:"policyresponse"`
}
//getRelativePath expects a path relative to project and builds the complete path
func getRelativePath(path string) string {
gp := os.Getenv("GOPATH")
ap := ospath.Join(gp, projectPath)
return ospath.Join(ap, path)
}
func loadScenario(t *testing.T, path string) (*scenarioT, error) {
fileBytes, err := loadFile(t, path)
if err != nil {
return nil, err
}
var testCases []scaseT
// load test cases seperated by '---'
// each test case defines an input & expected result
scenariosBytes := bytes.Split(fileBytes, []byte("---"))
for _, scenarioBytes := range scenariosBytes {
tc := scaseT{}
if err := yaml.Unmarshal([]byte(scenarioBytes), &tc); err != nil {
t.Errorf("failed to decode test case YAML: %v", err)
continue
}
testCases = append(testCases, tc)
}
scenario := scenarioT{
testCases: testCases,
}
return &scenario, nil
}
// loadFile loads file in byte buffer
func loadFile(t *testing.T, path string) ([]byte, error) {
path = getRelativePath(path)
t.Logf("reading file %s", path)
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
return ioutil.ReadFile(path)
}
//getFiles loads all scneario files in specified folder path
func getFiles(t *testing.T, folder string) ([]string, error) {
t.Logf("loading scneario files for folder %s", folder)
files, err := ioutil.ReadDir(folder)
if err != nil {
glog.Error(err)
return nil, err
}
var yamls []string
for _, file := range files {
if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
yamls = append(yamls, ospath.Join(folder, file.Name()))
}
}
return yamls, nil
}
func runScenario(t *testing.T, s *scenarioT) bool {
for _, tc := range s.testCases {
runTestCase(t, tc)
}
return true
}
func runTestCase(t *testing.T, tc scaseT) bool {
// var client *client.Client
// client, err := getClient(tc.Input.LoadResources)
// generate mock client if resources are to be loaded
// - create mock client
// - load resources
// client := getClient(t, tc.Input.LoadResources)
// apply policy
// convert policy -> kyverno.Policy
policy := loadPolicy(t, tc.Input.Policy)
// convert resource -> unstructured.Unstructured
resource := loadPolicyResource(t, tc.Input.Resource)
var er engine.EngineResponseNew
// Mutation
er = engine.MutateNew(*policy, *resource)
func() {
if data, err := json.Marshal(er.PolicyResponse); err == nil {
t.Log("-----engine response----")
t.Log(string(data))
// for _, r := range er.PolicyResponse.Rules {
// for _, p := range r.Patches {
// fmt.Println(string(p))
// }
// }
}
}()
// validate te response
t.Log("---Mutation---")
validateResource(t, er.PatchedResource, tc.Expected.Mutation.PatchedResource)
validateResponse(t, er.PolicyResponse, tc.Expected.Mutation.PolicyResponse)
// pass the patched resource from mutate to validate
if len(er.PolicyResponse.Rules) > 0 {
resource = &er.PatchedResource
}
// Validation
er = engine.ValidateNew(*policy, *resource)
func() {
if data, err := json.Marshal(er.PolicyResponse); err == nil {
t.Log(string(data))
fmt.Println(string(data))
}
}()
// validate the response
t.Log("---Validation---")
validateResponse(t, er.PolicyResponse, tc.Expected.Validation.PolicyResponse)
return true
}
func validateResource(t *testing.T, responseResource unstructured.Unstructured, expectedResourceFile string) {
resourcePrint := func(obj unstructured.Unstructured) {
t.Log("-----patched resource----")
if data, err := obj.MarshalJSON(); err == nil {
t.Log(string(data))
}
}
resourcePrint(responseResource)
if expectedResourceFile == "" {
t.Log("expected resource file not specified, wont compare resources")
return
}
// load expected resource
expectedResource := loadPolicyResource(t, expectedResourceFile)
if expectedResource == nil {
t.Log("failed to get the expected resource")
return
}
// resourcePrint(*expectedResource)
// compare the resources
if !reflect.DeepEqual(responseResource, *expectedResource) {
t.Log("failed: response resource returned does not match expected resource")
}
t.Log("success: response resource returned matches expected resource")
}
func validateResponse(t *testing.T, er engine.PolicyResponse, expected engine.PolicyResponse) {
if reflect.DeepEqual(expected, (engine.PolicyResponse{})) {
t.Log("no response expected")
return
}
// cant do deepEquals and the stats will be different, or we nil those fields and then do a comparison
// forcing only the fields that are specified to be comprared
// doing a field by fields comparsion will allow us to provied more details logs and granular error reporting
// check policy name is same :P
if er.Policy != expected.Policy {
t.Error("Policy: error")
}
// compare resource spec
compareResourceSpec(t, er.Resource, expected.Resource)
// // stats
// if er.RulesAppliedCount != expected.RulesAppliedCount {
// t.Log("RulesAppliedCount: error")
// }
// rules
if len(er.Rules) != len(er.Rules) {
t.Log("rule count: error")
}
if len(expected.Rules) == len(expected.Rules) {
// if there are rules being applied then we compare the rule response
// as the rules are applied in the order defined, the comparions of rules will be in order
for index, r := range expected.Rules {
compareRules(t, r, expected.Rules[index])
}
}
}
func compareResourceSpec(t *testing.T, resource engine.ResourceSpec, expectedResource engine.ResourceSpec) {
// kind
if resource.Kind != expectedResource.Kind {
t.Error("error: kind")
}
// // apiVersion
// if resource.APIVersion != expectedResource.APIVersion {
// t.Error("error: apiVersion")
// }
// namespace
if resource.Namespace != expectedResource.Namespace {
t.Error("error: namespace")
}
// name
if resource.Name != expectedResource.Name {
t.Error("error: name")
}
}
func compareRules(t *testing.T, rule engine.RuleResponse, expectedRule engine.RuleResponse) {
// name
if rule.Name != expectedRule.Name {
t.Errorf("error rule %s: name", rule.Name)
// as the rule names dont match no need to compare the rest of the information
return
}
// type
if rule.Type != expectedRule.Type {
t.Error("error: type")
}
// message
if rule.Message != expectedRule.Message {
t.Error("error: message")
}
// // patches
// if reflect.DeepEqual(rule.Patches, expectedRule.Patches) {
// t.Log("error: patches")
// }
// success
if rule.Success != expectedRule.Success {
t.Error("error: success")
}
// statistics
}
func loadPolicyResource(t *testing.T, file string) *unstructured.Unstructured {
// expect only one resource to be specified in the YAML
resources := loadResource(t, file)
if resources == nil {
t.Log("no resource specified")
return nil
}
if len(resources) > 1 {
t.Logf("more than one resource specified in the file %s", file)
t.Log("considering the first one for policy application")
}
return resources[0]
}
func getClient(t *testing.T, files []string) *client.Client {
if files == nil {
t.Log("no resources to load, not createing mock client")
return nil
}
var objects []runtime.Object
if files != nil {
glog.V(4).Infof("loading resources:")
for _, file := range files {
objects = loadObjects(t, file)
}
}
// create mock client
scheme := runtime.NewScheme()
// mock client expects the resource to be as runtime.Object
c, err := client.NewMockClient(scheme, objects...)
if err != nil {
t.Errorf("failed to create client. %v", err)
return nil
}
t.Log("created mock client with pre-loaded resources")
return c
}
func loadResource(t *testing.T, path string) []*unstructured.Unstructured {
var unstrResources []*unstructured.Unstructured
t.Logf("loading resource from %s", path)
data, err := loadFile(t, path)
if err != nil {
return nil
}
rBytes := bytes.Split(data, []byte("---"))
for _, r := range rBytes {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(r, nil, nil)
if err != nil {
t.Logf("failed to decode resource: %v", err)
continue
}
glog.Info(gvk)
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj)
if err != nil {
t.Logf("failed to unmarshall resource. %v", err)
continue
}
unstr := unstructured.Unstructured{Object: data}
t.Logf("loaded resource %s/%s/%s", unstr.GetKind(), unstr.GetNamespace(), unstr.GetName())
unstrResources = append(unstrResources, &unstr)
}
return unstrResources
}
func loadObjects(t *testing.T, path string) []runtime.Object {
var resources []runtime.Object
t.Logf("loading objects from %s", path)
data, err := loadFile(t, path)
if err != nil {
return nil
}
rBytes := bytes.Split(data, []byte("---"))
for _, r := range rBytes {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(r, nil, nil)
if err != nil {
t.Logf("failed to decode resource: %v", err)
continue
}
t.Log(gvk)
//TODO: add more details
t.Logf("loaded object %s", gvk.Kind)
resources = append(resources, obj)
}
return resources
}
func loadPolicy(t *testing.T, path string) *kyverno.Policy {
t.Logf("loading policy from %s", path)
data, err := loadFile(t, path)
if err != nil {
return nil
}
var policies []*kyverno.Policy
pBytes := bytes.Split(data, []byte("---"))
for _, p := range pBytes {
policy := kyverno.Policy{}
pBytes, err := apiyaml.ToJSON(p)
if err != nil {
glog.Error(err)
continue
}
if err := json.Unmarshal(pBytes, &policy); err != nil {
t.Logf("failed to marshall polic. %v", err)
continue
}
t.Logf("loaded policy %s", policy.Name)
policies = append(policies, &policy)
}
if len(policies) == 0 {
t.Log("no policies loaded")
return nil
}
if len(policies) > 1 {
t.Log("more than one policy defined, considering first for processing")
}
return policies[0]
}
func testScenario(t *testing.T, path string) {
//load scenario
scenario, err := loadScenario(t, path)
if err != nil {
t.Error(err)
return
}
runScenario(t, scenario)
}

View file

@ -1,258 +1,258 @@
package testrunner
import (
"fmt"
"reflect"
"strconv"
"testing"
// import (
// "fmt"
// "reflect"
// "strconv"
// "testing"
ospath "path"
// ospath "path"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
kscheme "k8s.io/client-go/kubernetes/scheme"
)
// "github.com/golang/glog"
// kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
// client "github.com/nirmata/kyverno/pkg/dclient"
// "github.com/nirmata/kyverno/pkg/engine"
// // "github.com/nirmata/kyverno/pkg/info"
// kscheme "k8s.io/client-go/kubernetes/scheme"
// )
type test struct {
ap string
t *testing.T
testCase *testCase
// input
policy *kyverno.Policy
tResource *resourceInfo
loadResources []*resourceInfo
// expected
genResources []*resourceInfo
patchedResource *resourceInfo
}
// type test struct {
// ap string
// t *testing.T
// testCase *testCase
// // input
// policy *kyverno.Policy
// tResource *resourceInfo
// loadResources []*resourceInfo
// // expected
// genResources []*resourceInfo
// patchedResource *resourceInfo
// }
func (t *test) run() {
var client *client.Client
var err error
//mock client is used if generate is defined
if t.testCase.Expected.Generation != nil {
// create mock client & load resources
client, err = createClient(t.loadResources)
if err != nil {
t.t.Errorf("Unable to create client. err %s", err)
}
// TODO: handle generate
// assuming its namespaces creation
decode := kscheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(t.tResource.rawResource), nil, nil)
_, err = client.CreateResource(t.tResource.gvk.Kind, "", obj, false)
if err != nil {
t.t.Errorf("error while creating namespace %s", err)
}
// func (t *test) run() {
// var client *client.Client
// var err error
// //mock client is used if generate is defined
// if t.testCase.Expected.Generation != nil {
// // create mock client & load resources
// client, err = createClient(t.loadResources)
// if err != nil {
// t.t.Errorf("Unable to create client. err %s", err)
// }
// // TODO: handle generate
// // assuming its namespaces creation
// decode := kscheme.Codecs.UniversalDeserializer().Decode
// obj, _, err := decode([]byte(t.tResource.rawResource), nil, nil)
// _, err = client.CreateResource(t.tResource.gvk.Kind, "", obj, false)
// if err != nil {
// t.t.Errorf("error while creating namespace %s", err)
// }
}
// apply the policy engine
pr, policyInfo, err := t.applyPolicy(t.policy, t.tResource, client)
if err != nil {
t.t.Error(err)
return
}
// Expected Result
// Test succesfuly ?
t.overAllPass(policyInfo.IsSuccessful(), t.testCase.Expected.Passes)
t.checkMutationResult(pr, policyInfo)
t.checkValidationResult(policyInfo)
t.checkGenerationResult(client, policyInfo)
}
// }
// // apply the policy engine
// pr, policyInfo, err := t.applyPolicy(t.policy, t.tResource, client)
// if err != nil {
// t.t.Error(err)
// return
// }
// // Expected Result
// // Test succesfuly ?
// t.overAllPass(policyInfo.IsSuccessful(), t.testCase.Expected.Passes)
// t.checkMutationResult(pr, policyInfo)
// t.checkValidationResult(policyInfo)
// t.checkGenerationResult(client, policyInfo)
// }
func (t *test) checkMutationResult(pr *resourceInfo, policyInfo info.PolicyInfo) {
if t.testCase.Expected.Mutation == nil {
glog.Info("No Mutation check defined")
return
}
// patched resource
if !compareResource(pr, t.patchedResource) {
fmt.Println(string(t.patchedResource.rawResource))
fmt.Println(string(pr.rawResource))
glog.Warningf("Expected resource %s ", string(pr.rawResource))
t.t.Error("Patched resources not as expected")
}
// func (t *test) checkMutationResult(pr *resourceInfo, policyInfo info.PolicyInfo) {
// if t.testCase.Expected.Mutation == nil {
// glog.Info("No Mutation check defined")
// return
// }
// // patched resource
// if !compareResource(pr, t.patchedResource) {
// fmt.Println(string(t.patchedResource.rawResource))
// fmt.Println(string(pr.rawResource))
// glog.Warningf("Expected resource %s ", string(pr.rawResource))
// t.t.Error("Patched resources not as expected")
// }
// check if rules match
t.compareRules(policyInfo.Rules, t.testCase.Expected.Mutation.Rules)
}
// // check if rules match
// t.compareRules(policyInfo.Rules, t.testCase.Expected.Mutation.Rules)
// }
func (t *test) overAllPass(result bool, expected string) {
b, err := strconv.ParseBool(expected)
if err != nil {
t.t.Error(err)
}
if result != b {
t.t.Errorf("Expected value %v and actual value %v dont match", expected, result)
}
}
// func (t *test) overAllPass(result bool, expected string) {
// b, err := strconv.ParseBool(expected)
// if err != nil {
// t.t.Error(err)
// }
// if result != b {
// t.t.Errorf("Expected value %v and actual value %v dont match", expected, result)
// }
// }
func (t *test) compareRules(ruleInfos []info.RuleInfo, rules []tRules) {
// Compare the rules specified in the expected against the actual rule info returned by the apply policy
for _, eRule := range rules {
// Look-up the rule from the policy info
rule := lookUpRule(eRule.Name, ruleInfos)
if reflect.DeepEqual(rule, info.RuleInfo{}) {
t.t.Errorf("Rule with name %s not found", eRule.Name)
continue
}
// get the corresponding rule
if rule.Name != eRule.Name {
t.t.Errorf("Rule Name not matching!. expected %s , actual %s", eRule.Name, rule.Name)
}
if rule.RuleType.String() != eRule.Type {
t.t.Errorf("Rule type mismatch!. expected %s, actual %s", eRule.Type, rule.RuleType.String())
}
if len(eRule.Messages) != len(rule.Msgs) {
t.t.Errorf("Number of rule messages not same. expected %d, actual %d", len(eRule.Messages), len(rule.Msgs))
}
for i, msg := range eRule.Messages {
if msg != rule.Msgs[i] {
t.t.Errorf("Messges dont match!. expected %s, actual %s", msg, rule.Msgs[i])
}
}
}
}
// func (t *test) compareRules(ruleInfos []info.RuleInfo, rules []tRules) {
// // Compare the rules specified in the expected against the actual rule info returned by the apply policy
// for _, eRule := range rules {
// // Look-up the rule from the policy info
// rule := lookUpRule(eRule.Name, ruleInfos)
// if reflect.DeepEqual(rule, info.RuleInfo{}) {
// t.t.Errorf("Rule with name %s not found", eRule.Name)
// continue
// }
// // get the corresponding rule
// if rule.Name != eRule.Name {
// t.t.Errorf("Rule Name not matching!. expected %s , actual %s", eRule.Name, rule.Name)
// }
// if rule.RuleType.String() != eRule.Type {
// t.t.Errorf("Rule type mismatch!. expected %s, actual %s", eRule.Type, rule.RuleType.String())
// }
// if len(eRule.Messages) != len(rule.Msgs) {
// t.t.Errorf("Number of rule messages not same. expected %d, actual %d", len(eRule.Messages), len(rule.Msgs))
// }
// for i, msg := range eRule.Messages {
// if msg != rule.Msgs[i] {
// t.t.Errorf("Messges dont match!. expected %s, actual %s", msg, rule.Msgs[i])
// }
// }
// }
// }
func lookUpRule(name string, ruleInfos []info.RuleInfo) info.RuleInfo {
// func lookUpRule(name string, ruleInfos []info.RuleInfo) info.RuleInfo {
for _, r := range ruleInfos {
if r.Name == name {
return r
}
}
return info.RuleInfo{}
}
// for _, r := range ruleInfos {
// if r.Name == name {
// return r
// }
// }
// return info.RuleInfo{}
// }
func (t *test) checkValidationResult(policyInfo info.PolicyInfo) {
if t.testCase.Expected.Validation == nil {
glog.Info("No Validation check defined")
return
}
// func (t *test) checkValidationResult(policyInfo info.PolicyInfo) {
// if t.testCase.Expected.Validation == nil {
// glog.Info("No Validation check defined")
// return
// }
// check if rules match
t.compareRules(policyInfo.Rules, t.testCase.Expected.Validation.Rules)
}
// // check if rules match
// t.compareRules(policyInfo.Rules, t.testCase.Expected.Validation.Rules)
// }
func (t *test) checkGenerationResult(client *client.Client, policyInfo info.PolicyInfo) {
if t.testCase.Expected.Generation == nil {
glog.Info("No Generate check defined")
return
}
if client == nil {
t.t.Error("client needs to be configured")
}
// func (t *test) checkGenerationResult(client *client.Client, policyInfo info.PolicyInfo) {
// if t.testCase.Expected.Generation == nil {
// glog.Info("No Generate check defined")
// return
// }
// if client == nil {
// t.t.Error("client needs to be configured")
// }
// check if rules match
t.compareRules(policyInfo.Rules, t.testCase.Expected.Generation.Rules)
// // check if rules match
// t.compareRules(policyInfo.Rules, t.testCase.Expected.Generation.Rules)
// check if the expected resources are generated
for _, r := range t.genResources {
n := ParseNameFromObject(r.rawResource)
ns := ParseNamespaceFromObject(r.rawResource)
_, err := client.GetResource(r.gvk.Kind, ns, n)
if err != nil {
t.t.Errorf("Resource %s/%s of kinf %s not found", ns, n, r.gvk.Kind)
}
// compare if the resources are same
//TODO: comapre []bytes vs unstrcutured resource
}
}
// // check if the expected resources are generated
// for _, r := range t.genResources {
// n := ParseNameFromObject(r.rawResource)
// ns := ParseNamespaceFromObject(r.rawResource)
// _, err := client.GetResource(r.gvk.Kind, ns, n)
// if err != nil {
// t.t.Errorf("Resource %s/%s of kinf %s not found", ns, n, r.gvk.Kind)
// }
// // compare if the resources are same
// //TODO: comapre []bytes vs unstrcutured resource
// }
// }
func (t *test) applyPolicy(policy *kyverno.Policy,
tresource *resourceInfo,
client *client.Client) (*resourceInfo, info.PolicyInfo, error) {
// apply policy on the trigger resource
// Mutate
var zeroPolicyInfo info.PolicyInfo
var err error
rawResource := tresource.rawResource
rname := engine.ParseNameFromObject(rawResource)
rns := engine.ParseNamespaceFromObject(rawResource)
rkind := engine.ParseKindFromObject(rawResource)
policyInfo := info.NewPolicyInfo(policy.Name,
rkind,
rname,
rns,
policy.Spec.ValidationFailureAction)
// func (t *test) applyPolicy(policy *kyverno.Policy,
// tresource *resourceInfo,
// client *client.Client) (*resourceInfo, info.PolicyInfo, error) {
// // apply policy on the trigger resource
// // Mutate
// var zeroPolicyInfo info.PolicyInfo
// var err error
// rawResource := tresource.rawResource
// rname := engine.ParseNameFromObject(rawResource)
// rns := engine.ParseNamespaceFromObject(rawResource)
// rkind := engine.ParseKindFromObject(rawResource)
// policyInfo := info.NewPolicyInfo(policy.Name,
// rkind,
// rname,
// rns,
// policy.Spec.ValidationFailureAction)
resource, err := ConvertToUnstructured(rawResource)
if err != nil {
return nil, zeroPolicyInfo, err
}
// resource, err := ConvertToUnstructured(rawResource)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
// Apply Mutation Rules
engineResponse := engine.Mutate(*policy, *resource)
// patches, ruleInfos := engine.Mutate(*policy, rawResource, *tresource.gvk)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// TODO: only validate if there are no errors in mutate, why?
if policyInfo.IsSuccessful() {
if len(engineResponse.Patches) != 0 {
rawResource, err = engine.ApplyPatches(rawResource, engineResponse.Patches)
if err != nil {
return nil, zeroPolicyInfo, err
}
}
}
// Validate
engineResponse = engine.Validate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if err != nil {
return nil, zeroPolicyInfo, err
}
// // Apply Mutation Rules
// engineResponse := engine.Mutate(*policy, *resource)
// // patches, ruleInfos := engine.Mutate(*policy, rawResource, *tresource.gvk)
// policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// // TODO: only validate if there are no errors in mutate, why?
// if policyInfo.IsSuccessful() {
// if len(engineResponse.Patches) != 0 {
// rawResource, err = engine.ApplyPatches(rawResource, engineResponse.Patches)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
// }
// }
// // Validate
// engineResponse = engine.Validate(*policy, *resource)
// policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
if rkind == "Namespace" {
if client != nil {
engineResponse := engine.Generate(client, *policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
}
}
// Generate
// transform the patched Resource into resource Info
ri, err := extractResourceRaw(rawResource)
if err != nil {
return nil, zeroPolicyInfo, err
}
// return the results
return ri, policyInfo, nil
}
// if rkind == "Namespace" {
// if client != nil {
// engineResponse := engine.Generate(client, *policy, *resource)
// policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// }
// }
// // Generate
// // transform the patched Resource into resource Info
// ri, err := extractResourceRaw(rawResource)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
// // return the results
// return ri, policyInfo, nil
// }
func NewTest(ap string, t *testing.T, tc *testCase) (*test, error) {
//---INPUT---
p, err := tc.loadPolicy(ospath.Join(ap, tc.Input.Policy))
if err != nil {
return nil, err
}
r, err := tc.loadTriggerResource(ap)
if err != nil {
return nil, err
}
// func NewTest(ap string, t *testing.T, tc *testCase) (*test, error) {
// //---INPUT---
// p, err := tc.loadPolicy(ospath.Join(ap, tc.Input.Policy))
// if err != nil {
// return nil, err
// }
// r, err := tc.loadTriggerResource(ap)
// if err != nil {
// return nil, err
// }
lr, err := tc.loadPreloadedResources(ap)
if err != nil {
return nil, err
}
// lr, err := tc.loadPreloadedResources(ap)
// if err != nil {
// return nil, err
// }
//---EXPECTED---
pr, err := tc.loadPatchedResource(ap)
if err != nil {
return nil, err
}
gr, err := tc.loadGeneratedResources(ap)
if err != nil {
return nil, err
}
return &test{
ap: ap,
t: t,
testCase: tc,
policy: p,
tResource: r,
loadResources: lr,
genResources: gr,
patchedResource: pr,
}, nil
}
// //---EXPECTED---
// pr, err := tc.loadPatchedResource(ap)
// if err != nil {
// return nil, err
// }
// gr, err := tc.loadGeneratedResources(ap)
// if err != nil {
// return nil, err
// }
// return &test{
// ap: ap,
// t: t,
// testCase: tc,
// policy: p,
// tResource: r,
// loadResources: lr,
// genResources: gr,
// patchedResource: pr,
// }, nil
// }

View file

@ -1,186 +1,186 @@
package testrunner
import (
"bytes"
"encoding/json"
"fmt"
ospath "path"
// import (
// "bytes"
// "encoding/json"
// "fmt"
// ospath "path"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes/scheme"
)
// "github.com/golang/glog"
// kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "k8s.io/apimachinery/pkg/runtime"
// yaml "k8s.io/apimachinery/pkg/util/yaml"
// "k8s.io/client-go/kubernetes/scheme"
// )
//testCase defines the input and the expected result
// it stores the path to the files that are to be loaded
// for references
type testCase struct {
Input *tInput `yaml:"input"`
Expected *tExpected `yaml:"expected"`
}
// //testCase defines the input and the expected result
// // it stores the path to the files that are to be loaded
// // for references
// type testCase struct {
// Input *tInput `yaml:"input"`
// Expected *tExpected `yaml:"expected"`
// }
// load resources store the resources that are pre-requisite
// for the test case and are pre-loaded in the client before
/// test case in evaluated
type tInput struct {
Policy string `yaml:"policy"`
Resource string `yaml:"resource"`
LoadResources []string `yaml:"load_resources,omitempty"`
}
// // load resources store the resources that are pre-requisite
// // for the test case and are pre-loaded in the client before
// /// test case in evaluated
// type tInput struct {
// Policy string `yaml:"policy"`
// Resource string `yaml:"resource"`
// LoadResources []string `yaml:"load_resources,omitempty"`
// }
type tExpected struct {
Passes string `yaml:"passes"`
Mutation *tMutation `yaml:"mutation,omitempty"`
Validation *tValidation `yaml:"validation,omitempty"`
Generation *tGeneration `yaml:"generation,omitempty"`
}
// type tExpected struct {
// Passes string `yaml:"passes"`
// Mutation *tMutation `yaml:"mutation,omitempty"`
// Validation *tValidation `yaml:"validation,omitempty"`
// Generation *tGeneration `yaml:"generation,omitempty"`
// }
type tMutation struct {
PatchedResource string `yaml:"patched_resource,omitempty"`
Rules []tRules `yaml:"rules"`
}
// type tMutation struct {
// PatchedResource string `yaml:"patched_resource,omitempty"`
// Rules []tRules `yaml:"rules"`
// }
type tValidation struct {
Rules []tRules `yaml:"rules"`
}
// type tValidation struct {
// Rules []tRules `yaml:"rules"`
// }
type tGeneration struct {
Resources []string `yaml:"resources"`
Rules []tRules `yaml:"rules"`
}
// type tGeneration struct {
// Resources []string `yaml:"resources"`
// Rules []tRules `yaml:"rules"`
// }
type tRules struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Messages []string `yaml:"messages"`
}
// type tRules struct {
// Name string `yaml:"name"`
// Type string `yaml:"type"`
// Messages []string `yaml:"messages"`
// }
type tResult struct {
Reason string `yaml:"reason, omitempty"`
}
// type tResult struct {
// Reason string `yaml:"reason, omitempty"`
// }
func (tc *testCase) policyEngineTest() {
// func (tc *testCase) policyEngineTest() {
}
func (tc *testCase) loadPreloadedResources(ap string) ([]*resourceInfo, error) {
return loadResources(ap, tc.Input.LoadResources...)
// return loadResources(ap, tc.Input.LoadResources...)
}
// }
// func (tc *testCase) loadPreloadedResources(ap string) ([]*resourceInfo, error) {
// return loadResources(ap, tc.Input.LoadResources...)
// // return loadResources(ap, tc.Input.LoadResources...)
// }
func (tc *testCase) loadGeneratedResources(ap string) ([]*resourceInfo, error) {
if tc.Expected.Generation == nil {
return nil, nil
}
return loadResources(ap, tc.Expected.Generation.Resources...)
}
// func (tc *testCase) loadGeneratedResources(ap string) ([]*resourceInfo, error) {
// if tc.Expected.Generation == nil {
// return nil, nil
// }
// return loadResources(ap, tc.Expected.Generation.Resources...)
// }
func (tc *testCase) loadPatchedResource(ap string) (*resourceInfo, error) {
if tc.Expected.Mutation == nil {
return nil, nil
}
rs, err := loadResources(ap, tc.Expected.Mutation.PatchedResource)
if err != nil {
return nil, err
}
if len(rs) != 1 {
glog.Warning("expects single resource mutation but multiple defined, will use first one")
}
return rs[0], nil
// func (tc *testCase) loadPatchedResource(ap string) (*resourceInfo, error) {
// if tc.Expected.Mutation == nil {
// return nil, nil
// }
// rs, err := loadResources(ap, tc.Expected.Mutation.PatchedResource)
// if err != nil {
// return nil, err
// }
// if len(rs) != 1 {
// glog.Warning("expects single resource mutation but multiple defined, will use first one")
// }
// return rs[0], nil
}
func (tc *testCase) loadResources(files []string) ([]*resourceInfo, error) {
lr := []*resourceInfo{}
for _, r := range files {
rs, err := loadResources(r)
if err != nil {
// return as test case will be invalid if a resource cannot be loaded
return nil, err
}
lr = append(lr, rs...)
}
return lr, nil
}
// }
// func (tc *testCase) loadResources(files []string) ([]*resourceInfo, error) {
// lr := []*resourceInfo{}
// for _, r := range files {
// rs, err := loadResources(r)
// if err != nil {
// // return as test case will be invalid if a resource cannot be loaded
// return nil, err
// }
// lr = append(lr, rs...)
// }
// return lr, nil
// }
func (tc *testCase) loadTriggerResource(ap string) (*resourceInfo, error) {
rs, err := loadResources(ap, tc.Input.Resource)
if err != nil {
return nil, err
}
if len(rs) != 1 {
glog.Warning("expects single resource trigger but multiple defined, will use first one")
}
return rs[0], nil
// func (tc *testCase) loadTriggerResource(ap string) (*resourceInfo, error) {
// rs, err := loadResources(ap, tc.Input.Resource)
// if err != nil {
// return nil, err
// }
// if len(rs) != 1 {
// glog.Warning("expects single resource trigger but multiple defined, will use first one")
// }
// return rs[0], nil
}
// }
// Loads a single policy
func (tc *testCase) loadPolicy(file string) (*kyverno.Policy, error) {
p := &kyverno.Policy{}
data, err := LoadFile(file)
if err != nil {
return nil, err
}
pBytes, err := yaml.ToJSON(data)
if err != nil {
return nil, err
}
if err := json.Unmarshal(pBytes, p); err != nil {
return nil, err
}
if p.TypeMeta.Kind != "Policy" {
return nil, fmt.Errorf("failed to parse policy")
}
return p, nil
}
// // Loads a single policy
// func (tc *testCase) loadPolicy(file string) (*kyverno.Policy, error) {
// p := &kyverno.Policy{}
// data, err := LoadFile(file)
// if err != nil {
// return nil, err
// }
// pBytes, err := yaml.ToJSON(data)
// if err != nil {
// return nil, err
// }
// if err := json.Unmarshal(pBytes, p); err != nil {
// return nil, err
// }
// if p.TypeMeta.Kind != "Policy" {
// return nil, fmt.Errorf("failed to parse policy")
// }
// return p, nil
// }
// loads multiple resources
func loadResources(ap string, args ...string) ([]*resourceInfo, error) {
ris := []*resourceInfo{}
for _, file := range args {
data, err := LoadFile(ospath.Join(ap, file))
if err != nil {
return nil, err
}
dd := bytes.Split(data, []byte(defaultYamlSeparator))
// resources seperated by yaml seperator
for _, d := range dd {
ri, err := extractResourceRaw(d)
if err != nil {
glog.Errorf("unable to load resource. err: %s ", err)
continue
}
ris = append(ris, ri)
}
}
return ris, nil
}
// // loads multiple resources
// func loadResources(ap string, args ...string) ([]*resourceInfo, error) {
// ris := []*resourceInfo{}
// for _, file := range args {
// data, err := LoadFile(ospath.Join(ap, file))
// if err != nil {
// return nil, err
// }
// dd := bytes.Split(data, []byte(defaultYamlSeparator))
// // resources seperated by yaml seperator
// for _, d := range dd {
// ri, err := extractResourceRaw(d)
// if err != nil {
// glog.Errorf("unable to load resource. err: %s ", err)
// continue
// }
// ris = append(ris, ri)
// }
// }
// return ris, nil
// }
func extractResourceRaw(d []byte) (*resourceInfo, error) {
// decode := scheme.Codecs.UniversalDeserializer().Decode
// obj, gvk, err := decode(d, nil, nil)
// if err != nil {
// return nil, err
// }
obj, gvk, err := extractResourceUnMarshalled(d)
// runtime.object to JSON
raw, err := json.Marshal(obj)
if err != nil {
return nil, err
}
return &resourceInfo{rawResource: raw,
gvk: gvk}, nil
}
// func extractResourceRaw(d []byte) (*resourceInfo, error) {
// // decode := scheme.Codecs.UniversalDeserializer().Decode
// // obj, gvk, err := decode(d, nil, nil)
// // if err != nil {
// // return nil, err
// // }
// obj, gvk, err := extractResourceUnMarshalled(d)
// // runtime.object to JSON
// raw, err := json.Marshal(obj)
// if err != nil {
// return nil, err
// }
// return &resourceInfo{rawResource: raw,
// gvk: gvk}, nil
// }
func extractResourceUnMarshalled(d []byte) (runtime.Object, *metav1.GroupVersionKind, error) {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(d, nil, nil)
if err != nil {
return nil, nil, err
}
return obj, &metav1.GroupVersionKind{Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind}, nil
}
// func extractResourceUnMarshalled(d []byte) (runtime.Object, *metav1.GroupVersionKind, error) {
// decode := scheme.Codecs.UniversalDeserializer().Decode
// obj, gvk, err := decode(d, nil, nil)
// if err != nil {
// return nil, nil, err
// }
// return obj, &metav1.GroupVersionKind{Group: gvk.Group,
// Version: gvk.Version,
// Kind: gvk.Kind}, nil
// }

View file

@ -1,98 +1,98 @@
package testrunner
import (
"bytes"
"io/ioutil"
"path/filepath"
"testing"
// import (
// "bytes"
// "io/ioutil"
// "path/filepath"
// "testing"
"os"
ospath "path"
// "os"
// ospath "path"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
)
// "github.com/golang/glog"
// "gopkg.in/yaml.v2"
// )
func runner(t *testing.T, relpath string) {
gp := os.Getenv("GOPATH")
ap := ospath.Join(gp, projectPath)
// build load scenarios
path := ospath.Join(ap, relpath)
// Load the scenario files
scenarioFiles := getYAMLfiles(path)
for _, secenarioFile := range scenarioFiles {
sc := newScenario(t, ap, secenarioFile)
if err := sc.load(); err != nil {
t.Error(err)
return
}
// run test cases
sc.run()
}
}
// func runner(t *testing.T, relpath string) {
// gp := os.Getenv("GOPATH")
// ap := ospath.Join(gp, projectPath)
// // build load scenarios
// path := ospath.Join(ap, relpath)
// // Load the scenario files
// scenarioFiles := getYAMLfiles(path)
// for _, secenarioFile := range scenarioFiles {
// sc := newScenario(t, ap, secenarioFile)
// if err := sc.load(); err != nil {
// t.Error(err)
// return
// }
// // run test cases
// sc.run()
// }
// }
type scenario struct {
ap string
t *testing.T
path string
tcs []*testCase
}
// type scenario struct {
// ap string
// t *testing.T
// path string
// tcs []*testCase
// }
func newScenario(t *testing.T, ap string, path string) *scenario {
return &scenario{
ap: ap,
t: t,
path: path,
}
}
// func newScenario(t *testing.T, ap string, path string) *scenario {
// return &scenario{
// ap: ap,
// t: t,
// path: path,
// }
// }
func getYAMLfiles(path string) (yamls []string) {
fileInfo, err := ioutil.ReadDir(path)
if err != nil {
return nil
}
for _, file := range fileInfo {
if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
yamls = append(yamls, ospath.Join(path, file.Name()))
}
}
return yamls
}
func (sc *scenario) load() error {
// read file
data, err := LoadFile(sc.path)
if err != nil {
return err
}
tcs := []*testCase{}
// load test cases seperated by '---'
// each test case defines an input & expected result
dd := bytes.Split(data, []byte(defaultYamlSeparator))
for _, d := range dd {
tc := &testCase{}
err := yaml.Unmarshal([]byte(d), tc)
if err != nil {
glog.Warningf("Error while decoding YAML object, err: %s", err)
continue
}
tcs = append(tcs, tc)
}
sc.tcs = tcs
return nil
}
// func getYAMLfiles(path string) (yamls []string) {
// fileInfo, err := ioutil.ReadDir(path)
// if err != nil {
// return nil
// }
// for _, file := range fileInfo {
// if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
// yamls = append(yamls, ospath.Join(path, file.Name()))
// }
// }
// return yamls
// }
// func (sc *scenario) load() error {
// // read file
// data, err := LoadFile(sc.path)
// if err != nil {
// return err
// }
// tcs := []*testCase{}
// // load test cases seperated by '---'
// // each test case defines an input & expected result
// dd := bytes.Split(data, []byte(defaultYamlSeparator))
// for _, d := range dd {
// tc := &testCase{}
// err := yaml.Unmarshal([]byte(d), tc)
// if err != nil {
// glog.Warningf("Error while decoding YAML object, err: %s", err)
// continue
// }
// tcs = append(tcs, tc)
// }
// sc.tcs = tcs
// return nil
// }
func (sc *scenario) run() {
if len(sc.tcs) == 0 {
sc.t.Error("No test cases to load")
return
}
// func (sc *scenario) run() {
// if len(sc.tcs) == 0 {
// sc.t.Error("No test cases to load")
// return
// }
for _, tc := range sc.tcs {
t, err := NewTest(sc.ap, sc.t, tc)
if err != nil {
sc.t.Error(err)
continue
}
t.run()
}
}
// for _, tc := range sc.tcs {
// t, err := NewTest(sc.ap, sc.t, tc)
// if err != nil {
// sc.t.Error(err)
// continue
// }
// t.run()
// }
// }

View file

@ -2,8 +2,37 @@ package testrunner
import "testing"
func TestCLI(t *testing.T) {
//https://github.com/nirmata/kyverno/issues/301
t.Skip("skipping testrunner as this needs a re-design")
runner(t, "/test/scenarios/cli")
// func TestCLI(t *testing.T) {
// //https://github.com/nirmata/kyverno/issues/301
// runner(t, "/test/scenarios/cli")
// }
func Test_Mutate_EndPoint(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_mutate_endPpoint.yaml")
}
func Test_Mutate_imagePullPolicy(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_mutate_imagePullPolicy.yaml")
}
func Test_Mutate_Validate_qos(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_mutate_validate_qos.yaml")
}
func Test_validate_containerSecurityContext(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_validate_containerSecurityContext.yaml")
}
func Test_validate_healthChecks(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_validate_healthChecks.yaml")
}
func Test_validate_imageRegistries(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_validate_imageRegistries.yaml")
}
func Test_validate_nonRootUsers(t *testing.T) {
testScenario(t, "/test/scenarios/test/scenario_validate_nonRootUser.yaml")
}
//TODO add tests for Generation

View file

@ -3,11 +3,10 @@ package webhooks
import (
"encoding/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/nirmata/kyverno/pkg/engine"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/info"
)
const (
@ -33,25 +32,27 @@ type response struct {
Value interface{} `json:"value"`
}
func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos []info.PolicyInfo) []byte {
annots := resource.GetAnnotations()
if annots == nil {
annots = map[string]string{}
func generateAnnotationPatches(annotations map[string]string, policyResponse engine.PolicyResponse) []byte {
if annotations == nil {
annotations = map[string]string{}
}
var patchResponse response
value := annotationFromPolicies(policyInfos)
if _, ok := annots[policyAnnotation]; ok {
value := generateAnnotationsFromPolicyResponse(policyResponse)
if value == nil {
// no patches or error while processing patches
return nil
}
if _, ok := annotations[policyAnnotation]; ok {
// create update patch string
patchResponse = response{
Op: "replace",
Path: "/metadata/annotations/" + policyAnnotation,
Op: "replace",
Value: string(value),
}
} else {
patchResponse = response{
Op: "add",
Path: "/metadata/annotations",
Op: "add",
Value: map[string]string{policyAnnotation: string(value)},
}
}
@ -67,47 +68,109 @@ func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos [
return patchByte
}
func annotationFromPolicies(policyInfos []info.PolicyInfo) []byte {
var policyPatches []policyPatch
for _, policyInfo := range policyInfos {
var pp policyPatch
// 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 generateAnnotationsFromPolicyResponse(policyResponse engine.PolicyResponse) []byte {
var rulePatches []rulePatch
// generate annotation for each mutation JSON patch to be applied on the resource
for _, rule := range policyResponse.Rules {
var patchmap map[string]string
patch := engine.JoinPatches(rule.Patches)
if err := json.Unmarshal(patch, &patchmap); err != nil {
glog.Errorf("Failed to parse patch bytes, err: %v\n", err)
continue
}
rp := rulePatch{
RuleName: rule.Name,
Op: patchmap["op"],
Path: patchmap["path"]}
rulePatches = append(rulePatches, rp)
glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches)
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)
patch, err := json.Marshal(rulePatches)
if err != nil {
glog.Infof("failed to marshall: %v", err)
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
return patch
}
// 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
// }

View file

@ -3,7 +3,6 @@ package webhooks
import (
"github.com/golang/glog"
engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -12,16 +11,19 @@ import (
)
// HandleMutation handles mutating webhook admission request
func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool, engine.EngineResponse) {
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool, []byte, string) {
glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
var patches [][]byte
var policyInfos []info.PolicyInfo
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, er engine.EngineResponse) {
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.MutationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
policyStats = append(policyStats, ps)
}
// send stats for aggregation
@ -32,31 +34,28 @@ func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool
ws.policyStatus.SendStat(stat)
}
}
glog.V(5).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
// convert RAW to unstructured
resource, err := engine.ConvertToUnstructured(request.Object.Raw)
if err != nil {
//TODO: skip applying the amiddions control ?
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
return true, nil, ""
}
//TODO: check if resource gvk is available in raw resource,
//TODO: check if the name and namespace is also passed right in the resource?
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
engineResponse := engine.EngineResponse{PatchedResource: *resource}
policies, err := ws.pLister.List(labels.NewSelector())
if err != nil {
//TODO check if the CRD is created ?
// Unable to connect to policy Lister to access policies
glog.Errorln("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
glog.Warning(err)
return true, engineResponse
return true, nil, ""
}
var engineResponses []engine.EngineResponseNew
for _, policy := range policies {
// check if policy has a rule for the admission request kind
@ -64,53 +63,39 @@ func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool
continue
}
policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
glog.V(4).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
engineResponse = engine.Mutate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// TODO: this can be
engineResponse := engine.MutateNew(*policy, *resource)
engineResponses = append(engineResponses, engineResponse)
// Gather policy application statistics
gatherStat(policy.Name, engineResponse)
// ps := policyctr.NewPolicyStat(policy.Name, engineResponse.ExecutionTime, nil, engineResponse.RulesAppliedCount)
if !policyInfo.IsSuccessful() {
gatherStat(policy.Name, engineResponse.PolicyResponse)
if !engineResponse.IsSuccesful() {
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s\n", policy.Name, resource.GetNamespace(), resource.GetName())
glog.V(4).Info("Failed rule details")
for _, r := range engineResponse.RuleInfos {
glog.V(4).Infof("%s: %s\n", r.Name, r.Msgs)
}
continue
}
patches = append(patches, engineResponse.Patches...)
policyInfos = append(policyInfos, policyInfo)
// gather patches
patches = append(patches, engineResponse.GetPatches()...)
// generate annotations
if annPatches := generateAnnotationPatches(resource.GetAnnotations(), engineResponse.PolicyResponse); annPatches != nil {
patches = append(patches, annPatches)
}
glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
//TODO: check if there is an order to policy application on resource
// resource = &engineResponse.PatchedResource
}
// ADD ANNOTATIONS
// ADD EVENTS
if len(patches) > 0 {
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
ws.eventGen.Add(eventsInfo...)
events := generateEvents(engineResponses, (request.Operation == v1beta1.Update))
ws.eventGen.Add(events...)
annotation := prepareAnnotationPatches(resource, policyInfos)
patches = append(patches, annotation)
}
ok, msg := isAdmSuccesful(policyInfos)
// Send policy engine Stats
if ok {
if isResponseSuccesful(engineResponses) {
sendStat(false)
engineResponse.Patches = patches
return true, engineResponse
patch := engine.JoinPatches(patches)
return true, patch, ""
}
sendStat(true)
glog.Errorf("Failed to mutate the resource: %s\n", msg)
return false, engineResponse
glog.Errorf("Failed to mutate the resource\n")
return false, nil, getErrorMsg(engineResponses)
}

View file

@ -3,51 +3,138 @@ package webhooks
import (
"strings"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/info"
)
//TODO: change validation from bool -> enum(validation, mutation)
func newEventInfoFromPolicyInfo(policyInfoList []info.PolicyInfo, onUpdate bool, ruleType info.RuleType) []*event.Info {
var eventsInfo []*event.Info
ok, msg := isAdmSuccesful(policyInfoList)
// Some policies failed to apply succesfully
if !ok {
for _, pi := range policyInfoList {
if pi.IsSuccessful() {
//generateEvents generates event info for the engine responses
func generateEvents(engineResponses []engine.EngineResponseNew, onUpdate bool) []event.Info {
var events []event.Info
if !isResponseSuccesful(engineResponses) {
for _, er := range engineResponses {
if er.IsSuccesful() {
// dont create events on success
continue
}
rules := pi.FailedRules()
ruleNames := strings.Join(rules, ";")
if !onUpdate {
// CREATE
eventsInfo = append(eventsInfo,
event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyApplyBlockCreate, pi.RNamespace+"/"+pi.RName, ruleNames))
glog.V(3).Infof("Rule(s) %s of policy %s blocked resource creation, error: %s\n", ruleNames, pi.Name, msg)
} else {
failedRules := er.GetFailedRules()
filedRulesStr := strings.Join(failedRules, ";")
if onUpdate {
var e event.Info
// UPDATE
eventsInfo = append(eventsInfo,
event.NewEvent(pi.RKind, pi.RNamespace, pi.RName, event.RequestBlocked, event.FPolicyApplyBlockUpdate, ruleNames, pi.Name))
eventsInfo = append(eventsInfo,
event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyBlockResourceUpdate, pi.RNamespace+"/"+pi.RName, ruleNames))
glog.V(3).Infof("Request blocked events info has prepared for %s/%s and %s/%s\n", policyKind, pi.Name, pi.RKind, pi.RName)
}
}
} else {
if !onUpdate {
// All policies were applied succesfully
// CREATE
for _, pi := range policyInfoList {
rules := pi.SuccessfulRules()
ruleNames := strings.Join(rules, ";")
eventsInfo = append(eventsInfo,
event.NewEvent(pi.RKind, pi.RNamespace, pi.RName, event.PolicyApplied, event.SRulesApply, ruleNames, pi.Name))
// event on resource
e = event.NewEventNew(
er.PolicyResponse.Resource.Kind,
er.PolicyResponse.Resource.APIVersion,
er.PolicyResponse.Resource.Namespace,
er.PolicyResponse.Resource.Name,
event.RequestBlocked.String(),
event.FPolicyApplyBlockUpdate,
filedRulesStr,
er.PolicyResponse.Policy,
)
glog.V(4).Infof("UPDATE event on resource %s/%s/%s with policy %s", er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name, er.PolicyResponse.Policy)
events = append(events, e)
glog.V(3).Infof("Success event info has prepared for %s/%s\n", pi.RKind, pi.RName)
// event on policy
e = event.NewEventNew(
"Policy",
kyverno.SchemeGroupVersion.String(),
"",
er.PolicyResponse.Policy,
event.RequestBlocked.String(),
event.FPolicyBlockResourceUpdate,
er.PolicyResponse.Resource.Namespace+"/"+er.PolicyResponse.Resource.Name,
filedRulesStr,
)
glog.V(4).Infof("UPDATE event on policy %s", er.PolicyResponse.Policy)
events = append(events, e)
} else {
// CREATE
// event on policy
e := event.NewEventNew(
"Policy",
kyverno.SchemeGroupVersion.String(),
"",
er.PolicyResponse.Policy,
event.RequestBlocked.String(),
event.FPolicyApplyBlockCreate,
er.PolicyResponse.Resource.Namespace+"/"+er.PolicyResponse.Resource.Name,
filedRulesStr,
)
glog.V(4).Infof("CREATE event on policy %s", er.PolicyResponse.Policy)
events = append(events, e)
}
}
return events
}
return eventsInfo
if !onUpdate {
// All policies were applied succesfully
// CREATE
for _, er := range engineResponses {
successRules := er.GetSuccessRules()
successRulesStr := strings.Join(successRules, ";")
// event on resource
e := event.NewEventNew(
er.PolicyResponse.Resource.Kind,
er.PolicyResponse.Resource.APIVersion,
er.PolicyResponse.Resource.Namespace,
er.PolicyResponse.Resource.Name,
event.PolicyApplied.String(),
event.SRulesApply,
successRulesStr,
er.PolicyResponse.Policy,
)
events = append(events, e)
}
}
return events
}
// //TODO: change validation from bool -> enum(validation, mutation)
// func newEventInfoFromPolicyInfo(policyInfoList []info.PolicyInfo, onUpdate bool, ruleType info.RuleType) []*event.Info {
// var eventsInfo []*event.Info
// ok, msg := isAdmSuccesful(policyInfoList)
// // Some policies failed to apply succesfully
// if !ok {
// for _, pi := range policyInfoList {
// if pi.IsSuccessful() {
// continue
// }
// rules := pi.FailedRules()
// ruleNames := strings.Join(rules, ";")
// if !onUpdate {
// // CREATE
// eventsInfo = append(eventsInfo,
// event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyApplyBlockCreate, pi.RNamespace+"/"+pi.RName, ruleNames))
// glog.V(3).Infof("Rule(s) %s of policy %s blocked resource creation, error: %s\n", ruleNames, pi.Name, msg)
// } else {
// // UPDATE
// eventsInfo = append(eventsInfo,
// event.NewEvent(pi.RKind, pi.RNamespace, pi.RName, event.RequestBlocked, event.FPolicyApplyBlockUpdate, ruleNames, pi.Name))
// eventsInfo = append(eventsInfo,
// event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyBlockResourceUpdate, pi.RNamespace+"/"+pi.RName, ruleNames))
// glog.V(3).Infof("Request blocked events info has prepared for %s/%s and %s/%s\n", policyKind, pi.Name, pi.RKind, pi.RName)
// }
// }
// } else {
// if !onUpdate {
// // All policies were applied succesfully
// // CREATE
// for _, pi := range policyInfoList {
// rules := pi.SuccessfulRules()
// ruleNames := strings.Join(rules, ";")
// eventsInfo = append(eventsInfo,
// event.NewEvent(pi.RKind, pi.RNamespace, pi.RName, event.PolicyApplied, event.SRulesApply, ruleNames, pi.Name))
// glog.V(3).Infof("Success event info has prepared for %s/%s\n", pi.RKind, pi.RName)
// }
// }
// }
// return eventsInfo
// }

View file

@ -10,8 +10,6 @@ import (
"net/http"
"time"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/golang/glog"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha1"
@ -24,6 +22,7 @@ import (
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/webhookconfig"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
)
@ -52,7 +51,7 @@ func NewWebhookServer(
client *client.Client,
tlsPair *tlsutils.TlsPemPair,
pInformer kyvernoinformer.PolicyInformer,
pvInormer kyvernoinformer.PolicyViolationInformer,
pvInformer kyvernoinformer.PolicyViolationInformer,
eventGen event.Interface,
webhookRegistrationClient *webhookconfig.WebhookRegistrationClient,
policyStatus policy.PolicyStatusInterface,
@ -75,8 +74,8 @@ func NewWebhookServer(
client: client,
kyvernoClient: kyvernoClient,
pLister: pInformer.Lister(),
pvLister: pvInormer.Lister(),
pListerSynced: pInformer.Informer().HasSynced,
pvLister: pvInformer.Lister(),
pListerSynced: pvInformer.Informer().HasSynced,
pvListerSynced: pInformer.Informer().HasSynced,
eventGen: eventGen,
webhookRegistrationClient: webhookRegistrationClient,
@ -141,24 +140,43 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
}
func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var response *v1beta1.AdmissionResponse
allowed, engineResponse := ws.handleMutation(request)
if !allowed {
// TODO: add failure message to response
// MUTATION
ok, patches, msg := ws.HandleMutation(request)
if !ok {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
response = ws.handleValidation(request, engineResponse.PatchedResource)
if response.Allowed && len(engineResponse.Patches) > 0 {
patchType := v1beta1.PatchTypeJSONPatch
response.Patch = engine.JoinPatches(engineResponse.Patches)
response.PatchType = &patchType
// patch the resource with patches before handling validation rules
patchedResource := processResourceWithPatches(patches, request.Object.Raw)
// VALIDATION
ok, msg = ws.HandleValidation(request, patchedResource)
if !ok {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
return response
// Succesfful processing of mutation & validation rules in policy
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Status: "Success",
},
Patch: patches,
PatchType: &patchType,
}
}
// RunAsync TLS server in separate thread and returns control immediately

View file

@ -6,24 +6,61 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/engine"
)
const policyKind = "Policy"
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)
// 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, ";")
// }
func isResponseSuccesful(engineReponses []engine.EngineResponseNew) bool {
for _, er := range engineReponses {
if !er.IsSuccesful() {
return false
}
}
return admSuccess, strings.Join(errMsgs, ";")
return true
}
// returns true -> if there is even one policy that blocks resource requst
// returns false -> if all the policies are meant to report only, we dont block resource request
func toBlockResource(engineReponses []engine.EngineResponseNew) bool {
for _, er := range engineReponses {
if er.PolicyResponse.ValidationFailureAction != ReportViolation {
glog.V(4).Infof("ValidationFailureAction set to enforce for policy %s , blocking resource ceation", er.PolicyResponse.Policy)
return true
}
}
glog.V(4).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
return false
}
func getErrorMsg(engineReponses []engine.EngineResponseNew) string {
var str []string
for _, er := range engineReponses {
if !er.IsSuccesful() {
str = append(str, fmt.Sprintf("failed policy %s", er.PolicyResponse.Policy))
for _, rule := range er.PolicyResponse.Rules {
if !rule.Success {
str = append(str, rule.ToString())
}
}
}
}
return strings.Join(str, "\n")
}
//ArrayFlags to store filterkinds
@ -75,15 +112,28 @@ const (
ReportViolation = "audit"
)
// returns true -> if there is even one policy that blocks resource requst
// returns false -> if all the policies are meant to report only, we dont block resource request
func toBlock(pis []info.PolicyInfo) bool {
for _, pi := range pis {
if pi.ValidationFailureAction != ReportViolation {
glog.V(3).Infoln("ValidationFailureAction set to enforce, blocking resource ceation")
return true
}
// // returns true -> if there is even one policy that blocks resource requst
// // returns false -> if all the policies are meant to report only, we dont block resource request
// func toBlock(pis []info.PolicyInfo) bool {
// for _, pi := range pis {
// if pi.ValidationFailureAction != ReportViolation {
// glog.V(3).Infoln("ValidationFailureAction set to enforce, blocking resource ceation")
// return true
// }
// }
// glog.V(3).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
// return false
// }
func processResourceWithPatches(patch []byte, resource []byte) []byte {
if patch == nil {
return nil
}
glog.V(3).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
return false
glog.Info(string(resource))
resource, err := engine.ApplyPatchNew(resource, patch)
if err != nil {
glog.Errorf("failed to patch resource: %v", err)
return nil
}
return resource
}

View file

@ -3,28 +3,29 @@ package webhooks
import (
"github.com/golang/glog"
engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/policyviolation"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// 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, resource unstructured.Unstructured) *v1beta1.AdmissionResponse {
var policyInfos []info.PolicyInfo
// patchedResource is the (resource + patches) after applying mutation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, patchedResource []byte) (bool, string) {
glog.V(4).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, er engine.EngineResponse) {
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.ValidationExecutionTime = er.ExecutionTime
ps.Stats.RulesAppliedCount = er.RulesAppliedCount
ps.Stats.ValidationExecutionTime = policyResponse.ProcessingTime
ps.Stats.RulesAppliedCount = policyResponse.RulesAppliedCount
policyStats = append(policyStats, ps)
}
// send stats for aggregation
@ -36,8 +37,23 @@ func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, res
}
}
glog.V(5).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
resourceRaw := request.Object.Raw
if patchedResource != nil {
glog.V(4).Info("using patched resource from mutation to process validation rules")
resourceRaw = patchedResource
}
// convert RAW to unstructured
resource, err := engine.ConvertToUnstructured(resourceRaw)
if err != nil {
//TODO: skip applying the amiddions control ?
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
return true, ""
}
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
// all the patches to be applied on the resource
policies, err := ws.pLister.List(labels.NewSelector())
if err != nil {
@ -45,81 +61,46 @@ func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, res
// 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 &v1beta1.AdmissionResponse{
Allowed: true,
}
return true, ""
}
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
// all the patches to be applied on the resource
var engineResponses []engine.EngineResponseNew
for _, policy := range policies {
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
continue
}
policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
glog.V(4).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
// glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
engineResponse := engine.Validate(*policy, resource)
if len(engineResponse.RuleInfos) == 0 {
engineResponse := engine.ValidateNew(*policy, *resource)
engineResponses = append(engineResponses, engineResponse)
// Gather policy application statistics
gatherStat(policy.Name, engineResponse.PolicyResponse)
if !engineResponse.IsSuccesful() {
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s\n", policy.Name, resource.GetNamespace(), resource.GetName())
continue
}
gatherStat(policy.Name, engineResponse)
if len(engineResponse.RuleInfos) > 0 {
glog.V(4).Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
}
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if !policyInfo.IsSuccessful() {
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, resource.GetNamespace(), resource.GetName())
for _, r := range engineResponse.RuleInfos {
glog.Warningf("%s: %s\n", r.Name, r.Msgs)
}
}
policyInfos = append(policyInfos, policyInfo)
}
// ADD EVENTS
if len(policyInfos) > 0 && len(policyInfos[0].Rules) != 0 {
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Validation)
// If the validationFailureAction flag is set "audit",
// then we dont block the request and report the violations
ws.eventGen.Add(eventsInfo...)
}
events := generateEvents(engineResponses, (request.Operation == v1beta1.Update))
ws.eventGen.Add(events...)
// If Validation fails then reject the request
// violations are created if "audit" flag is set
// and if there are any then we dont block the resource creation
// Even if one the policy being applied
ok, msg := isAdmSuccesful(policyInfos)
if !ok && toBlock(policyInfos) {
if !isResponseSuccesful(engineResponses) && toBlockResource(engineResponses) {
sendStat(true)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: msg,
},
}
return false, getErrorMsg(engineResponses)
}
// ADD POLICY VIOLATIONS
policyviolation.GeneratePolicyViolations(ws.pvListerSynced, ws.pvLister, ws.kyvernoClient, policyInfos)
policyviolation.CreatePV(ws.pvLister, ws.kyvernoClient, engineResponses)
sendStat(false)
return &v1beta1.AdmissionResponse{
Allowed: true,
}
return true, ""
}

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Endpoints
metadata:
creationTimestamp:
labels:
isMutated: 'true'
label: test
name: test-endpoint
subsets:
- addresses:
- ip: 192.168.10.171
ports:
- name: secure-connection
port: 9663
protocol: TCP

View file

@ -0,0 +1,20 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginxlatest
spec:
replicas: 1
selector:
matchLabels:
app: nginxlatest
template:
metadata:
labels:
app: nginxlatest
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent

View file

@ -0,0 +1,27 @@
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp:
labels:
test: qos
name: qos-demo
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: nginx
resources:
limits:
cpu: 50m
memory: 300Mi
status: {}

View file

@ -0,0 +1,21 @@
# file path relative to project root
input:
policy: examples/cli/policy_deployment.yaml
resource: examples/cli/nginx.yaml
expected:
mutation:
patchedresource: test/output/nginx.yaml
policyresponse:
policy: policy-deployment
resource:
kind: Deployment
apiVersion: 'apps/v1'
namespace: ''
name: nginx-deployment
rules:
- name: add-label
type: Mutation
success: true
message: succesfully process JSON patches
# patches: `[{"path":"/metadata/labels/isMutated","op":"add","value":"true"},
# {"path":"/metadata/labels/app","op":"replace","value":"nginx_is_mutated"}]`

View file

@ -0,0 +1,19 @@
# file path relative to project root
input:
policy: examples/policy_mutate_endpoint.yaml
resource: examples/resource_mutate_endpoint.yaml
expected:
mutation:
patchedresource: test/output/output_mutate_endpoint.yaml
policyresponse:
policy: policy-endpoints
resource:
kind: Endpoints
apiVersion: v1
namespace: ''
name: test-endpoint
rules:
- name: pEP
type: Mutation
success: true
message: succesfully process JSON patches

View file

@ -0,0 +1,19 @@
# file path relative to project root
input:
policy: examples/policy_mutate_imagePullPolicy.yaml
resource: examples/resource_mutate_imagePullPolicy.yaml
expected:
mutation:
patchedresource: test/output/output_mutate_endpoint.yaml
policyresponse:
policy: image-pull-policy
resource:
kind: Deployment
apiVersion: apps/v1
namespace: ''
name: nginx-deployment
rules:
- name: image-pull-policy
type: Mutation
success: true
message: succesfully process JSON patches

View file

@ -0,0 +1,32 @@
# file path relative to project root
input:
policy: examples/policy_mutate_validate_qos.yaml
resource: examples/resource_mutate_validate_qos.yaml
expected:
mutation:
patchedresource: test/output/output_mutate_validate_qos.yaml
policyresponse:
policy: policy-qos
resource:
kind: Deployment
apiVersion: apps/v1
namespace: ''
name: qos-demo
rules:
- name: add-memory-limit
type: Mutation
success: true
message: succesfully process JSON patches
validation:
policyresponse:
policy: policy-qos
resource:
kind: Deployment
apiVersion: apps/v1
namespace: ''
name: qos-demo
rules:
- name: check-cpu-memory-limits
type: Validation
meesage: validation pattern succesfully validated
success: true

View file

@ -0,0 +1,18 @@
# file path relative to project root
input:
policy: examples/policy_validate_containerSecurityContext.yaml
resource: examples/resource_validate_containerSecurityContext.yaml
expected:
validation:
policyresponse:
policy: container-security-context
resource:
kind: Deployment
apiVersion: apps/v1
namespace: ''
name: csc-demo-unprivileged
rules:
- name: validate-user-privilege
type: Validation
meesage: validation pattern succesfully validated
success: true

View file

@ -0,0 +1,22 @@
# file path relative to project root
input:
policy: examples/policy_validate_healthChecks.yaml
resource: examples/resource_validate_healthChecks.yaml
expected:
validation:
policyresponse:
policy: check-probe-exists
resource:
kind: Pod
apiVersion: v1
namespace: ''
name: probe
rules:
- name: check-readinessProbe-exists
type: Validation
meesage: validation pattern succesfully validated
success: true
- name: check-livenessProbe-exists
type: Validation
meesage: validation pattern succesfully validated
success: true

View file

@ -0,0 +1,18 @@
# file path relative to project root
input:
policy: examples/policy_validate_imageRegistries.yaml
resource: examples/resource_validate_imageRegistries.yaml
expected:
validation:
policyresponse:
policy: check-registries
resource:
kind: Deployment
apiVersion: apps/v1
namespace: ''
name: nirmata-nginx
rules:
- name: check-registries
type: Validation
meesage: validation pattern succesfully validated
success: true

View file

@ -0,0 +1,18 @@
# file path relative to project root
input:
policy: examples/policy_validate_nonRootUser.yaml
resource: examples/resource_validate_nonRootUser.yaml
expected:
validation:
policyresponse:
policy: check-container-security-context
resource:
kind: Pod
apiVersion: v1
namespace: ''
name: sec-ctx-unprivileged
rules:
- name: check-root-user
type: Validation
meesage: 1/2 patterns succesfully validated
success: true