1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

refactor engine packages for validate & generate

This commit is contained in:
shivkumar dudhani 2019-12-12 15:02:59 -08:00
parent 507c43ddca
commit b5de11fc0e
34 changed files with 2220 additions and 2068 deletions

View file

@ -1,28 +1,29 @@
package engine
package anchor
import (
"fmt"
"strconv"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
//ValidationHandler for element processes
type ValidationHandler interface {
Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error)
Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error)
}
type resourceElementHandler = func(resourceElement, patternElement, originPattern interface{}, path string) (string, error)
//CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
switch {
case anchor.IsConditionAnchor(element):
case IsConditionAnchor(element):
return NewConditionAnchorHandler(element, pattern, path)
case anchor.IsExistanceAnchor(element):
case IsExistanceAnchor(element):
return NewExistanceHandler(element, pattern, path)
case anchor.IsEqualityAnchor(element):
case IsEqualityAnchor(element):
return NewEqualityHandler(element, pattern, path)
case anchor.IsNegationAnchor(element):
case IsNegationAnchor(element):
return NewNegationHandler(element, pattern, path)
default:
return NewDefaultHandler(element, pattern, path)
@ -46,7 +47,7 @@ type NegationHandler struct {
}
//Handle process negation handler
func (nh NegationHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(nh.anchor)
currentPath := nh.path + anchorKey + "/"
// if anchor is present in the resource then fail
@ -75,13 +76,13 @@ type EqualityHandler struct {
}
//Handle processed condition anchor
func (eh EqualityHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (eh EqualityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
returnPath, err := handler(value, eh.pattern, originPattern, currentPath)
if err != nil {
return returnPath, err
}
@ -107,14 +108,14 @@ type DefaultHandler struct {
}
//Handle process non anchor element
func (dh DefaultHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
currentPath := dh.path + dh.element + "/"
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
} else {
path, err := validateResourceElement(resourceMap[dh.element], dh.pattern, originPattern, currentPath)
path, err := handler(resourceMap[dh.element], dh.pattern, originPattern, currentPath)
if err != nil {
return path, err
}
@ -139,13 +140,13 @@ type ConditionAnchorHandler struct {
}
//Handle processed condition anchor
func (ch ConditionAnchorHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
anchorKey := removeAnchor(ch.anchor)
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
returnPath, err := handler(value, ch.pattern, originPattern, currentPath)
if err != nil {
return returnPath, err
}
@ -172,7 +173,7 @@ type ExistanceHandler struct {
}
//Handle processes the existence anchor handler
func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
func (eh ExistanceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
// skip is used by existance anchor to not process further if condition is not satisfied
anchorKey := removeAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
@ -191,7 +192,7 @@ func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPatt
if !ok {
return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
}
return validateExistenceListResource(typedResource, typedPatternMap, originPattern, currentPath)
return validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath)
default:
glog.Error("Invalid type: Existance ^ () anchor can be used only on list/array type resource")
return currentPath, fmt.Errorf("Invalid resource type %T: Existance ^ () anchor can be used only on list/array type resource", value)
@ -200,12 +201,12 @@ func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPatt
return "", nil
}
func validateExistenceListResource(resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
// the idea is atleast on the elements in the array should satisfy the pattern
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
_, err := handler(resourceElement, patternMap, originPattern, currentPath)
if err == nil {
// condition is satisfied, dont check further
glog.V(4).Infof("Existence check satisfied at path %s, for pattern %v", currentPath, patternMap)
@ -216,11 +217,11 @@ func validateExistenceListResource(resourceList []interface{}, patternMap map[st
return path, fmt.Errorf("Existence anchor validation failed at path %s", path)
}
func getAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
func GetAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) || anchor.IsEqualityAnchor(key) || anchor.IsNegationAnchor(key) {
if IsConditionAnchor(key) || IsExistanceAnchor(key) || IsEqualityAnchor(key) || IsNegationAnchor(key) {
anchors[key] = value
continue
}

View file

@ -52,3 +52,16 @@ func IsExistanceAnchor(str string) bool {
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
func removeAnchor(key string) string {
if IsConditionAnchor(key) {
return key[1 : len(key)-1]
}
if IsExistanceAnchor(key) || IsAddingAnchor(key) || IsEqualityAnchor(key) || IsNegationAnchor(key) {
return key[2 : len(key)-1]
}
return key
}

View file

@ -10,7 +10,7 @@ import (
//Interface ... normal functions
type Interface interface {
Add(key string, data []byte, force bool) error
Add(key string, data []byte) error
Remove(key string) error
EvalInterface
}

View file

@ -56,3 +56,33 @@ func Test_Add(t *testing.T) {
t.Error("exected result does not match")
}
}
func Test_AddUser(t *testing.T) {
rawUser := []byte(`
{
"userInfo": {
"username": "admin",
"uid": "014fbff9a07c",
"groups": ["system:authenticated","my-admin-group"],
"extra": {
"some-key":["some-value1", "some-value2"]
}
}
}
`)
expectedResult := "admin"
var err error
ctx := NewContext()
ctx.Add("user", rawUser)
query := "user.userInfo.username"
result, err := ctx.Query(query)
if err != nil {
t.Error(err)
}
t.Log(expectedResult)
t.Log(result)
if !reflect.DeepEqual(expectedResult, result) {
t.Error("exected result does not match")
}
}

View file

@ -0,0 +1,130 @@
package generate
import (
"time"
"fmt"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/validate"
"github.com/nirmata/kyverno/pkg/engine/variables"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
//ApplyRuleGenerator apply generate rules
func ApplyRuleGenerator(ctx context.EvalInterface, client *client.Client, ns unstructured.Unstructured, rule kyverno.Rule, policyCreationTime metav1.Time) (resp response.RuleResponse) {
startTime := time.Now()
glog.V(4).Infof("started applying generation rule %q (%v)", rule.Name, startTime)
resp.Name = rule.Name
resp.Type = "Generation"
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying generation rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
}()
var err error
resource := &unstructured.Unstructured{}
var rdata map[string]interface{}
// To manage existing resource , we compare the creation time for the default resource to be generate and policy creation time
processExisting := func() bool {
nsCreationTime := ns.GetCreationTimestamp()
return nsCreationTime.Before(&policyCreationTime)
}()
if rule.Generation.Data != nil {
// perform variable substituion in generate resource pattern
newData := variables.SubstituteVariables(ctx, rule.Generation.Data)
glog.V(4).Info("generate rule: creates new resource")
// 1> Check if resource exists
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", 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(ctx, newData, obj)
if err != nil {
glog.V(4).Infof("generate rule: unable to check if configuration %v, is present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
resp.Success = false
resp.Message = fmt.Sprintf("unable to check if configuration %v, is present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return resp
}
if !ok {
glog.V(4).Infof("generate rule: configuration %v not present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
resp.Success = false
resp.Message = fmt.Sprintf("configuration %v not present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return resp
}
resp.Success = true
resp.Message = fmt.Sprintf("required configuration %v is present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return resp
}
rdata, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&newData)
if err != nil {
glog.Error(err)
resp.Success = false
resp.Message = fmt.Sprintf("failed to parse the specified resource spec %v: %v", newData, err)
return resp
}
}
if rule.Generation.Clone != (kyverno.CloneFrom{}) {
glog.V(4).Info("generate rule: clone resource")
// 1> Check if resource exists
_, err := client.GetResource(rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
if err == nil {
glog.V(4).Infof("generate rule: resource '%s/%s' already present in namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
resp.Success = true
resp.Message = fmt.Sprintf("resource '%s/%s' already present in namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return resp
}
// 2> If clone already exists return
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' not present in namespace '%s': %v", rule.Generation.Kind, rule.Generation.Clone.Name, rule.Generation.Clone.Namespace, err)
resp.Success = false
resp.Message = fmt.Sprintf("clone reference resource '%s/%s' not present in namespace '%s': %v", rule.Generation.Kind, rule.Generation.Clone.Name, rule.Generation.Clone.Namespace, err)
return resp
}
glog.V(4).Infof("generate rule: clone reference resource '%s/%s' present in namespace '%s'", rule.Generation.Kind, rule.Generation.Clone.Name, rule.Generation.Clone.Namespace)
rdata = resource.UnstructuredContent()
}
if processExisting {
glog.V(4).Infof("resource '%s/%s' not found in existing namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
resp.Success = false
resp.Message = fmt.Sprintf("resource '%s/%s' not found in existing namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
// for existing resources we generate an error which indirectly generates a policy violation
return resp
}
resource.SetUnstructuredContent(rdata)
resource.SetName(rule.Generation.Name)
resource.SetNamespace(ns.GetName())
// Reset resource version
resource.SetResourceVersion("")
_, 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", rule.Generation.Kind, resource.GetNamespace(), resource.GetName(), err)
resp.Success = false
resp.Message = fmt.Sprintf("unable to create resource %s/%s/%s: %v", rule.Generation.Kind, resource.GetNamespace(), resource.GetName(), err)
return resp
}
glog.V(4).Infof("generate rule: created resource %s/%s/%s", rule.Generation.Kind, resource.GetNamespace(), resource.GetName())
resp.Success = true
resp.Message = fmt.Sprintf("created resource %s/%s/%s", rule.Generation.Kind, resource.GetNamespace(), resource.GetName())
return resp
}
//checkResource checks if the config is present in th eresource
func checkResource(ctx context.EvalInterface, config interface{}, resource *unstructured.Unstructured) (bool, error) {
// we are checking if config is a subset of resource with default pattern
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, config)
if err != nil {
glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err)
return false, err
}
return true, nil
}

View file

@ -3,164 +3,48 @@ package engine
import (
"time"
"fmt"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/nirmata/kyverno/pkg/engine/generate"
"github.com/nirmata/kyverno/pkg/engine/response"
)
//Generate apply generation rules on a resource
func Generate(policyContext PolicyContext) (response EngineResponse) {
func Generate(policyContext PolicyContext) (resp response.EngineResponse) {
policy := policyContext.Policy
ns := policyContext.NewResource
client := policyContext.Client
ctx := policyContext.Context
startTime := time.Now()
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
resp.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = ns.GetName()
response.PolicyResponse.Resource.Kind = ns.GetKind()
response.PolicyResponse.Resource.APIVersion = ns.GetAPIVersion()
resp.PolicyResponse.Resource.Name = ns.GetName()
resp.PolicyResponse.Resource.Kind = ns.GetKind()
resp.PolicyResponse.Resource.APIVersion = ns.GetAPIVersion()
}()
glog.V(4).Infof("started applying generation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
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)
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying generation rules policy %v (%v)", policy.Name, resp.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Generation Rules appplied succesfully count %v for policy %q", resp.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.PolicyResponse.RulesAppliedCount++
resp.PolicyResponse.RulesAppliedCount++
}
for _, rule := range policy.Spec.Rules {
if !rule.HasGenerate() {
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())
ruleResponse := applyRuleGenerator(client, ns, rule, policy.GetCreationTimestamp())
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
ruleResponse := generate.ApplyRuleGenerator(ctx, client, ns, rule, policy.GetCreationTimestamp())
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
// set resource in reponse
response.PatchedResource = ns
return response
}
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{}
// To manage existing resource , we compare the creation time for the default resource to be generate and policy creation time
processExisting := func() bool {
nsCreationTime := ns.GetCreationTimestamp()
return nsCreationTime.Before(&policyCreationTime)
}()
if rule.Generation.Data != nil {
glog.V(4).Info("generate rule: creates new resource")
// 1> Check if resource exists
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", 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(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' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
response.Success = false
response.Message = fmt.Sprintf("unable to check if configuration %v, is present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return response
}
if !ok {
glog.V(4).Infof("generate rule: configuration %v not present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
response.Success = false
response.Message = fmt.Sprintf("configuration %v not present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return response
}
response.Success = true
response.Message = fmt.Sprintf("required configuration %v is present in resource '%s/%s' in namespace '%s'", rule.Generation.Data, rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return response
}
rdata, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&rule.Generation.Data)
if err != nil {
glog.Error(err)
response.Success = false
response.Message = fmt.Sprintf("failed to parse the specified resource spec %v: %v", rule.Generation.Data, err)
return response
}
}
if rule.Generation.Clone != (kyverno.CloneFrom{}) {
glog.V(4).Info("generate rule: clone resource")
// 1> Check if resource exists
_, err := client.GetResource(rule.Generation.Kind, ns.GetName(), rule.Generation.Name)
if err == nil {
glog.V(4).Infof("generate rule: resource '%s/%s' already present in namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
response.Success = true
response.Message = fmt.Sprintf("resource '%s/%s' already present in namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
return response
}
// 2> If clone already exists return
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' not present in namespace '%s': %v", rule.Generation.Kind, rule.Generation.Clone.Name, rule.Generation.Clone.Namespace, err)
response.Success = false
response.Message = fmt.Sprintf("clone reference resource '%s/%s' not present in namespace '%s': %v", rule.Generation.Kind, rule.Generation.Clone.Name, rule.Generation.Clone.Namespace, err)
return response
}
glog.V(4).Infof("generate rule: clone reference resource '%s/%s' present in namespace '%s'", rule.Generation.Kind, rule.Generation.Clone.Name, rule.Generation.Clone.Namespace)
rdata = resource.UnstructuredContent()
}
if processExisting {
glog.V(4).Infof("resource '%s/%s' not found in existing namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
response.Success = false
response.Message = fmt.Sprintf("resource '%s/%s' not found in existing namespace '%s'", rule.Generation.Kind, rule.Generation.Name, ns.GetName())
// for existing resources we generate an error which indirectly generates a policy violation
return response
}
resource.SetUnstructuredContent(rdata)
resource.SetName(rule.Generation.Name)
resource.SetNamespace(ns.GetName())
// Reset resource version
resource.SetResourceVersion("")
_, 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", 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", 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
func checkResource(config interface{}, resource *unstructured.Unstructured) (bool, error) {
// pass empty context
ctx := context.NewContext()
// we are checking if config is a subset of resource with default pattern
path, err := validateResourceWithPattern(ctx, resource.Object, config)
if err != nil {
glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err)
return false, err
}
return true, nil
resp.PatchedResource = ns
return resp
}

View file

@ -4,10 +4,11 @@ import (
"time"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/response"
)
// Mutate performs mutation. Overlay first and then mutation patches
func Mutate(policyContext PolicyContext) (response EngineResponse) {
func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
startTime := time.Now()
policy := policyContext.Policy
resource := policyContext.NewResource
@ -15,22 +16,22 @@ func Mutate(policyContext PolicyContext) (response EngineResponse) {
// policy information
func() {
// set policy information
response.PolicyResponse.Policy = policy.Name
resp.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()
resp.PolicyResponse.Resource.Name = resource.GetName()
resp.PolicyResponse.Resource.Namespace = resource.GetNamespace()
resp.PolicyResponse.Resource.Kind = resource.GetKind()
resp.PolicyResponse.Resource.APIVersion = resource.GetAPIVersion()
}()
glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
defer func() {
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 count %v for policy %q", response.PolicyResponse.RulesAppliedCount, policy.Name)
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying mutation rules policy %v (%v)", policy.Name, resp.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Mutation Rules appplied count %v for policy %q", resp.PolicyResponse.RulesAppliedCount, policy.Name)
}()
incrementAppliedRuleCount := func() {
// rules applied succesfully count
response.PolicyResponse.RulesAppliedCount++
resp.PolicyResponse.RulesAppliedCount++
}
patchedResource := policyContext.NewResource
@ -59,7 +60,7 @@ func Mutate(policyContext PolicyContext) (response EngineResponse) {
}
// Process Overlay
if rule.Mutation.Overlay != nil {
var ruleResponse RuleResponse
var ruleResponse response.RuleResponse
ruleResponse, patchedResource = processOverlay(rule, patchedResource)
if ruleResponse.Success == true && ruleResponse.Patches == nil {
// overlay pattern does not match the resource conditions
@ -69,20 +70,20 @@ func Mutate(policyContext PolicyContext) (response EngineResponse) {
glog.Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
}
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
// Process Patches
if rule.Mutation.Patches != nil {
var ruleResponse RuleResponse
var ruleResponse response.RuleResponse
ruleResponse, patchedResource = processPatches(rule, patchedResource)
glog.Infof("Mutate patches in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
}
}
// send the patched resource
response.PatchedResource = patchedResource
return response
resp.PatchedResource = patchedResource
return resp
}

View file

@ -15,17 +15,19 @@ import (
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/validate"
)
// processOverlay processes validation patterns on the resource
func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (response RuleResponse, patchedResource unstructured.Unstructured) {
func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp 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()
resp.Name = rule.Name
resp.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)
resp.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying overlay rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
}()
patches, overlayerr := processOverlayPatches(resource.UnstructuredContent(), rule.Mutation.Overlay)
@ -36,41 +38,41 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
// consider as success
case conditionNotPresent:
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
response.Success = true
return response, resource
resp.Success = true
return resp, resource
// conditions are not met, don't apply this rule
case conditionFailure:
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
//TODO: send zero response and not consider this as applied?
response.Success = true
response.Message = overlayerr.ErrorMsg()
return response, resource
resp.Success = true
resp.Message = overlayerr.ErrorMsg()
return resp, resource
// rule application failed
case overlayFailure:
glog.Errorf("Resource %s/%s/%s: failed to process overlay: %v in the rule %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg(), rule.Name)
response.Success = false
response.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
return response, resource
resp.Success = false
resp.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
return resp, resource
default:
glog.Errorf("Resource %s/%s/%s: Unknown type of error: %v", resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.Error())
response.Success = false
response.Message = fmt.Sprintf("Unknown type of error: %v", overlayerr.Error())
return response, resource
resp.Success = false
resp.Message = fmt.Sprintf("Unknown type of error: %v", overlayerr.Error())
return resp, resource
}
}
if len(patches) == 0 {
response.Success = true
return response, resource
resp.Success = true
return resp, resource
}
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
response.Success = false
resp.Success = false
glog.Infof("unable to marshall resource: %v", err)
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
}
var patchResource []byte
@ -78,25 +80,25 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
if err != nil {
msg := fmt.Sprintf("failed to apply JSON patches: %v", err)
glog.V(2).Infof("%s, patches=%s", msg, string(JoinPatches(patches)))
response.Success = false
response.Message = msg
return response, resource
resp.Success = false
resp.Message = msg
return resp, 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
resp.Success = false
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
}
// rule application succesfuly
response.Success = true
response.Message = fmt.Sprintf("successfully processed overlay")
response.Patches = patches
resp.Success = true
resp.Message = fmt.Sprintf("successfully processed overlay")
resp.Patches = patches
// apply the patches to the resource
return response, patchedResource
return resp, patchedResource
}
func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayError) {
@ -464,3 +466,21 @@ func hasNestedAnchors(overlay interface{}) bool {
return false
}
}
// Checks if array object matches anchors. If not - skip - return true
func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
if !validate.ValidateValueWithPattern(value, pattern) {
return true
}
}
return false
}

View file

@ -7,6 +7,7 @@ import (
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/engine/validate"
)
func meetConditions(resource, overlay interface{}) (string, overlayError) {
@ -138,7 +139,7 @@ func compareOverlay(resource, overlay interface{}, path string) (string, overlay
}
}
case string, float64, int, int64, bool, nil:
if !ValidateValueWithPattern(resource, overlay) {
if !validate.ValidateValueWithPattern(resource, overlay) {
glog.V(4).Infof("Mutate rule: failed validating value %v with overlay %v", resource, overlay)
return path, newOverlayError(conditionFailure, fmt.Sprintf("Failed validating value %v with overlay %v", resource, overlay))
}

View file

@ -6,11 +6,11 @@ import (
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/response"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
@ -67,23 +67,23 @@ func ApplyPatchNew(resource, patch []byte) ([]byte, error) {
}
func processPatches(rule kyverno.Rule, resource unstructured.Unstructured) (response RuleResponse, patchedResource unstructured.Unstructured) {
func processPatches(rule kyverno.Rule, resource unstructured.Unstructured) (resp 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()
resp.Name = rule.Name
resp.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)
resp.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished JSON patch rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
}()
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
response.Success = false
resp.Success = false
glog.Infof("unable to marshall resource: %v", err)
response.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return response, resource
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
}
var errs []error
@ -112,27 +112,27 @@ func processPatches(rule kyverno.Rule, resource unstructured.Unstructured) (resp
// error while processing JSON patches
if len(errs) > 0 {
response.Success = false
response.Message = fmt.Sprintf("failed to process JSON patches: %v", func() string {
resp.Success = false
resp.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
return resp, 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
resp.Success = false
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
}
// JSON patches processed succesfully
response.Success = true
response.Message = fmt.Sprintf("succesfully process JSON patches")
response.Patches = patches
return response, patchedResource
resp.Success = true
resp.Message = fmt.Sprintf("succesfully process JSON patches")
resp.Patches = patches
return resp, patchedResource
}

View file

@ -1,4 +1,4 @@
package engine
package response
import (
"fmt"

View file

@ -287,23 +287,23 @@ func isStringIsReference(str string) bool {
return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')'
}
// Checks if array object matches anchors. If not - skip - return true
func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
// // Checks if array object matches anchors. If not - skip - return true
// func skipArrayObject(object, anchors map[string]interface{}) bool {
// for key, pattern := range anchors {
// key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
// value, ok := object[key]
// if !ok {
// return true
// }
if !ValidateValueWithPattern(value, pattern) {
return true
}
}
// if !ValidateValueWithPattern(value, pattern) {
// return true
// }
// }
return false
}
// return false
// }
// removeAnchor remove special characters around anchored key
func removeAnchor(key string) string {

View file

@ -1,6 +1,7 @@
package engine
import (
"encoding/json"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -8,6 +9,25 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestGetAnchorsFromMap_ThereAreNoAnchors(t *testing.T) {
rawMap := []byte(`{
"name":"nirmata-*",
"notAnchor1":123,
"namespace":"kube-?olicy",
"notAnchor2":"sample-text",
"object":{
"key1":"value1",
"(key2)":"value2"
}
}`)
var unmarshalled map[string]interface{}
json.Unmarshal(rawMap, &unmarshalled)
actualMap := getAnchorsFromMap(unmarshalled)
assert.Assert(t, len(actualMap) == 0)
}
// Match multiple kinds
func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
rawResource := []byte(`{

View file

@ -0,0 +1,52 @@
package validate
import (
"fmt"
"strconv"
"github.com/nirmata/kyverno/pkg/engine/operator"
)
func isStringIsReference(str string) bool {
if len(str) < len(operator.ReferenceSign) {
return false
}
return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')'
}
// convertToFloat converts string and any other value to float64
func convertToFloat(value interface{}) (float64, error) {
switch typed := value.(type) {
case string:
var err error
floatValue, err := strconv.ParseFloat(typed, 64)
if err != nil {
return 0, err
}
return floatValue, nil
case float64:
return typed, nil
case int64:
return float64(typed), nil
case int:
return float64(typed), nil
default:
return 0, fmt.Errorf("Could not convert %T to float64", value)
}
}
func getRawKeyIfWrappedWithAttributes(str string) string {
if len(str) < 2 {
return str
}
if str[0] == '(' && str[len(str)-1] == ')' {
return str[1 : len(str)-1]
} else if (str[0] == '$' || str[0] == '^' || str[0] == '+' || str[0] == '=') && (str[1] == '(' && str[len(str)-1] == ')') {
return str[2 : len(str)-1]
} else {
return str
}
}

View file

@ -1,4 +1,4 @@
package engine
package validate
import (
"math"

View file

@ -1,4 +1,4 @@
package engine
package validate
import (
"encoding/json"
@ -15,6 +15,48 @@ func TestValidateValueWithPattern_Bool(t *testing.T) {
assert.Assert(t, ValidateValueWithPattern(false, false))
}
func TestValidateString_AsteriskTest(t *testing.T) {
pattern := "*"
value := "anything"
empty := ""
assert.Assert(t, validateString(value, pattern, operator.Equal))
assert.Assert(t, validateString(empty, pattern, operator.Equal))
}
func TestValidateString_LeftAsteriskTest(t *testing.T) {
pattern := "*right"
value := "leftright"
right := "right"
assert.Assert(t, validateString(value, pattern, operator.Equal))
assert.Assert(t, validateString(right, pattern, operator.Equal))
value = "leftmiddle"
middle := "middle"
assert.Assert(t, !validateString(value, pattern, operator.Equal))
assert.Assert(t, !validateString(middle, pattern, operator.Equal))
}
func TestValidateString_MiddleAsteriskTest(t *testing.T) {
pattern := "ab*ba"
value := "abbeba"
assert.Assert(t, validateString(value, pattern, operator.Equal))
value = "abbca"
assert.Assert(t, !validateString(value, pattern, operator.Equal))
}
func TestValidateString_QuestionMark(t *testing.T) {
pattern := "ab?ba"
value := "abbba"
assert.Assert(t, validateString(value, pattern, operator.Equal))
value = "abbbba"
assert.Assert(t, !validateString(value, pattern, operator.Equal))
}
func TestValidateValueWithPattern_BoolInJson(t *testing.T) {
rawPattern := []byte(`
{

View file

@ -0,0 +1,270 @@
package validate
import (
"errors"
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/operator"
"github.com/nirmata/kyverno/pkg/engine/variables"
)
// validateResourceWithPattern is a start of element-by-element validation process
// It assumes that validation is started from root, so "/" is passed
//TODO: for failure, we return the path at which it failed along with error
func ValidateResourceWithPattern(ctx context.EvalInterface, resource, pattern interface{}) (string, error) {
// first pass we substitute all the JMESPATH substitution for the variable
// variable: {{<JMESPATH>}}
// if a JMESPATH fails, we dont return error but variable is substitured with nil and error log
pattern = variables.SubstituteVariables(ctx, pattern)
return validateResourceElement(resource, pattern, pattern, "/")
}
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
// and calls corresponding handler
// Pattern tree and resource tree can have different structure. In this case validation fails
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) (string, error) {
var err error
switch typedPatternElement := patternElement.(type) {
// map
case map[string]interface{}:
typedResourceElement, ok := resourceElement.(map[string]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
}
return validateMap(typedResourceElement, typedPatternElement, originPattern, path)
// array
case []interface{}:
typedResourceElement, ok := resourceElement.([]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
return path, fmt.Errorf("Validation rule Failed at path %s, resource does not satisfy the expected overlay pattern", path)
}
return validateArray(typedResourceElement, typedPatternElement, originPattern, path)
// elementary values
case string, float64, int, int64, bool, nil:
/*Analyze pattern */
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String {
if isStringIsReference(checkedPattern.String()) { //check for $ anchor
patternElement, err = actualizePattern(originPattern, checkedPattern.String(), path)
if err != nil {
return path, err
}
}
}
if !ValidateValueWithPattern(resourceElement, patternElement) {
return path, fmt.Errorf("Validation rule failed at '%s' to validate value %v with pattern %v", path, resourceElement, patternElement)
}
default:
glog.V(4).Infof("Pattern contains unknown type %T. Path: %s", patternElement, path)
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
}
return "", nil
}
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) {
// check if there is anchor in pattern
// Phase 1 : Evaluate all the anchors
// Phase 2 : Evaluate non-anchors
anchors, resources := anchor.GetAnchorsResourcesFromMap(patternMap)
// Evaluate anchors
for key, patternElement := range anchors {
// get handler for each pattern in the pattern
// - Conditional
// - Existance
// - Equality
handler := anchor.CreateElementHandler(key, patternElement, path)
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern)
// if there are resource values at same level, then anchor acts as conditional instead of a strict check
// but if there are non then its a if then check
if err != nil {
// If Conditional anchor fails then we dont process the resources
if anchor.IsConditionAnchor(key) {
glog.V(4).Infof("condition anchor did not satisfy, wont process the resources: %s", err)
return "", nil
}
return handlerPath, err
}
}
// Evaluate resources
for key, resourceElement := range resources {
// get handler for resources in the pattern
handler := anchor.CreateElementHandler(key, resourceElement, path)
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern)
if err != nil {
return handlerPath, err
}
}
return "", nil
}
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) (string, error) {
if 0 == len(patternArray) {
return path, fmt.Errorf("Pattern Array empty")
}
switch typedPatternElement := patternArray[0].(type) {
case map[string]interface{}:
// This is special case, because maps in arrays can have anchors that must be
// processed with the special way affecting the entire array
path, err := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
if err != nil {
return path, err
}
default:
// In all other cases - detect type and handle each array element with validateResourceElement
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
path, err := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
if err != nil {
return path, err
}
}
}
return "", nil
}
func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, error) {
var foundValue interface{}
referencePattern = strings.Trim(referencePattern, "$()")
operatorVariable := operator.GetOperatorFromStringPattern(referencePattern)
referencePattern = referencePattern[len(operatorVariable):]
if len(referencePattern) == 0 {
return nil, errors.New("Expected path. Found empty reference")
}
// Check for variables
// substitute it from Context
// remove abosolute path
// {{ }}
// value :=
actualPath := formAbsolutePath(referencePattern, absolutePath)
valFromReference, err := getValueFromReference(origPattern, actualPath)
if err != nil {
return err, nil
}
//TODO validate this
if operatorVariable == operator.Equal { //if operator does not exist return raw value
return valFromReference, nil
}
foundValue, err = valFromReferenceToString(valFromReference, string(operatorVariable))
if err != nil {
return "", err
}
return string(operatorVariable) + foundValue.(string), nil
}
//Parse value to string
func valFromReferenceToString(value interface{}, operator string) (string, error) {
switch typed := value.(type) {
case string:
return typed, nil
case int, int64:
return fmt.Sprintf("%d", value), nil
case float64:
return fmt.Sprintf("%f", value), nil
default:
return "", fmt.Errorf("Incorrect expression. Operator %s does not match with value: %v", operator, value)
}
}
// returns absolute path
func formAbsolutePath(referencePath, absolutePath string) string {
if filepath.IsAbs(referencePath) {
return referencePath
}
return filepath.Join(absolutePath, referencePath)
}
//Prepares original pattern, path to value, and call traverse function
func getValueFromReference(origPattern interface{}, reference string) (interface{}, error) {
originalPatternMap := origPattern.(map[string]interface{})
reference = reference[1:len(reference)]
statements := strings.Split(reference, "/")
return getValueFromPattern(originalPatternMap, statements, 0)
}
func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, error) {
for key, pattern := range patternMap {
rawKey := getRawKeyIfWrappedWithAttributes(key)
if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 {
return pattern, nil
} else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 {
continue
}
switch typedPattern := pattern.(type) {
case []interface{}:
if keys[currentKeyIndex] == rawKey {
for i, value := range typedPattern {
resourceMap, ok := value.(map[string]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Expected %T, found %T", pattern, value)
return nil, fmt.Errorf("Validation rule failed, resource does not have expected pattern %v", patternMap)
}
if keys[currentKeyIndex+1] == strconv.Itoa(i) {
return getValueFromPattern(resourceMap, keys, currentKeyIndex+2)
}
return nil, errors.New("Reference to non-existent place in the document")
}
}
return nil, errors.New("Reference to non-existent place in the document")
case map[string]interface{}:
if keys[currentKeyIndex] == rawKey {
return getValueFromPattern(typedPattern, keys, currentKeyIndex+1)
}
return nil, errors.New("Reference to non-existent place in the document")
case string, float64, int, int64, bool, nil:
continue
}
}
path := ""
/*for i := len(keys) - 1; i >= 0; i-- {
path = keys[i] + path + "/"
}*/
for _, elem := range keys {
path = "/" + elem + path
}
return nil, fmt.Errorf("No value found for specified reference: %s", path)
}
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
for i, resourceElement := range resourceMapArray {
// check the types of resource element
// expect it to be map, but can be anything ?:(
currentPath := path + strconv.Itoa(i) + "/"
returnpath, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
if err != nil {
return returnpath, err
}
}
return "", nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,48 +1,44 @@
package engine
import (
"errors"
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/anchor"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/operator"
"github.com/nirmata/kyverno/pkg/engine/variables"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/validate"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func startResultResponse(response *EngineResponse, policy kyverno.ClusterPolicy, newR unstructured.Unstructured) {
func startResultResponse(resp *response.EngineResponse, policy kyverno.ClusterPolicy, newR unstructured.Unstructured) {
// set policy information
response.PolicyResponse.Policy = policy.Name
resp.PolicyResponse.Policy = policy.Name
// resource details
response.PolicyResponse.Resource.Name = newR.GetName()
response.PolicyResponse.Resource.Namespace = newR.GetNamespace()
response.PolicyResponse.Resource.Kind = newR.GetKind()
response.PolicyResponse.Resource.APIVersion = newR.GetAPIVersion()
response.PolicyResponse.ValidationFailureAction = policy.Spec.ValidationFailureAction
resp.PolicyResponse.Resource.Name = newR.GetName()
resp.PolicyResponse.Resource.Namespace = newR.GetNamespace()
resp.PolicyResponse.Resource.Kind = newR.GetKind()
resp.PolicyResponse.Resource.APIVersion = newR.GetAPIVersion()
resp.PolicyResponse.ValidationFailureAction = policy.Spec.ValidationFailureAction
}
func endResultResponse(response *EngineResponse, startTime time.Time) {
response.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("Finished applying validation rules policy %v (%v)", response.PolicyResponse.Policy, response.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", response.PolicyResponse.RulesAppliedCount, response.PolicyResponse.Policy)
func endResultResponse(resp *response.EngineResponse, startTime time.Time) {
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("Finished applying validation rules policy %v (%v)", resp.PolicyResponse.Policy, resp.PolicyResponse.ProcessingTime)
glog.V(4).Infof("Validation Rules appplied succesfully count %v for policy %q", resp.PolicyResponse.RulesAppliedCount, resp.PolicyResponse.Policy)
}
func incrementAppliedCount(response *EngineResponse) {
func incrementAppliedCount(resp *response.EngineResponse) {
// rules applied succesfully count
response.PolicyResponse.RulesAppliedCount++
resp.PolicyResponse.RulesAppliedCount++
}
//Validate applies validation rules from policy on the resource
func Validate(policyContext PolicyContext) (response EngineResponse) {
func Validate(policyContext PolicyContext) (resp response.EngineResponse) {
startTime := time.Now()
policy := policyContext.Policy
newR := policyContext.NewResource
@ -57,21 +53,21 @@ func Validate(policyContext PolicyContext) (response EngineResponse) {
if reflect.DeepEqual(oldR, unstructured.Unstructured{}) {
// Create Mode
// Operate on New Resource only
response := validate(ctx, policy, newR, admissionInfo)
startResultResponse(response, policy, newR)
defer endResultResponse(response, startTime)
resp := validateResource(ctx, policy, newR, admissionInfo)
startResultResponse(resp, policy, newR)
defer endResultResponse(resp, startTime)
// set PatchedResource with orgin resource if empty
// in order to create policy violation
if reflect.DeepEqual(response.PatchedResource, unstructured.Unstructured{}) {
response.PatchedResource = newR
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
resp.PatchedResource = newR
}
return *response
return *resp
}
// Update Mode
// Operate on New and Old Resource only
// New resource
oldResponse := validate(ctx, policy, oldR, admissionInfo)
newResponse := validate(ctx, policy, newR, admissionInfo)
oldResponse := validateResource(ctx, policy, oldR, admissionInfo)
newResponse := validateResource(ctx, policy, newR, admissionInfo)
// if the old and new response is same then return empty response
if !isSameResponse(oldResponse, newResponse) {
@ -85,11 +81,11 @@ func Validate(policyContext PolicyContext) (response EngineResponse) {
}
// if there are no changes with old and new response then sent empty response
// skip processing
return EngineResponse{}
return response.EngineResponse{}
}
func validate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo RequestInfo) *EngineResponse {
response := &EngineResponse{}
func validateResource(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo RequestInfo) *response.EngineResponse {
resp := &response.EngineResponse{}
for _, rule := range policy.Spec.Rules {
if !rule.HasValidate() {
continue
@ -112,26 +108,26 @@ func validate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource
}
if rule.Validation.Pattern != nil || rule.Validation.AnyPattern != nil {
ruleResponse := validatePatterns(ctx, resource, rule)
incrementAppliedCount(response)
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
incrementAppliedCount(resp)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
}
}
return response
return resp
}
func isSameResponse(oldResponse, newResponse *EngineResponse) bool {
func isSameResponse(oldResponse, newResponse *response.EngineResponse) bool {
// if the respones are same then return true
return isSamePolicyResponse(oldResponse.PolicyResponse, newResponse.PolicyResponse)
}
func isSamePolicyResponse(oldPolicyRespone, newPolicyResponse PolicyResponse) bool {
func isSamePolicyResponse(oldPolicyRespone, newPolicyResponse response.PolicyResponse) bool {
// can skip policy and resource checks as they will be same
// compare rules
return isSameRules(oldPolicyRespone.Rules, newPolicyResponse.Rules)
}
func isSameRules(oldRules []RuleResponse, newRules []RuleResponse) bool {
func isSameRules(oldRules []response.RuleResponse, newRules []response.RuleResponse) bool {
if len(oldRules) != len(newRules) {
return false
}
@ -159,32 +155,32 @@ func isSameRules(oldRules []RuleResponse, newRules []RuleResponse) bool {
}
// validatePatterns validate pattern and anyPattern
func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructured, rule kyverno.Rule) (response RuleResponse) {
func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructured, rule kyverno.Rule) (resp 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()
resp.Name = rule.Name
resp.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)
resp.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying validation rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
}()
// either pattern or anyPattern can be specified in Validation rule
if rule.Validation.Pattern != nil {
path, err := validateResourceWithPattern(ctx, resource.Object, rule.Validation.Pattern)
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, rule.Validation.Pattern)
if err != nil {
// rule application failed
glog.V(4).Infof("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s: %v", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message, err)
response.Success = false
response.Message = fmt.Sprintf("Validation error: %s\nValidation rule '%s' failed at path '%s'.",
resp.Success = false
resp.Message = fmt.Sprintf("Validation error: %s\nValidation rule '%s' failed at path '%s'.",
rule.Validation.Message, rule.Name, path)
return response
return resp
}
// 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 rule '%s' succeeded.", rule.Name)
return response
resp.Success = true
resp.Message = fmt.Sprintf("Validation rule '%s' succeeded.", rule.Name)
return resp
}
// using anyPattern we can define multiple patterns and only one of them has to be succesfully validated
@ -192,13 +188,13 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
var errs []error
var failedPaths []string
for index, pattern := range rule.Validation.AnyPattern {
path, err := validateResourceWithPattern(ctx, resource.Object, pattern)
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, pattern)
if err == nil {
// this pattern was succesfully validated
glog.V(4).Infof("anyPattern %v succesfully validated on resource %s/%s/%s", pattern, resource.GetKind(), resource.GetNamespace(), resource.GetName())
response.Success = true
response.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, index)
return response
resp.Success = true
resp.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, index)
return resp
}
if err != nil {
glog.V(4).Infof("Validation error: %s\nValidation rule %s anyPattern[%d] failed at path %s for %s/%s/%s",
@ -210,271 +206,17 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
// If none of the anyPatterns are validated
if len(errs) > 0 {
glog.V(4).Infof("none of anyPattern were processed: %v", errs)
response.Success = false
resp.Success = false
var errorStr []string
for index, err := range errs {
glog.V(4).Infof("anyPattern[%d] failed at path %s: %v", index, failedPaths[index], err)
str := fmt.Sprintf("Validation rule %s anyPattern[%d] failed at path %s.", rule.Name, index, failedPaths[index])
errorStr = append(errorStr, str)
}
response.Message = fmt.Sprintf("Validation error: %s\n%s", rule.Validation.Message, strings.Join(errorStr, "\n"))
resp.Message = fmt.Sprintf("Validation error: %s\n%s", rule.Validation.Message, strings.Join(errorStr, "\n"))
return response
return resp
}
}
return RuleResponse{}
}
// validateResourceWithPattern is a start of element-by-element validation process
// It assumes that validation is started from root, so "/" is passed
//TODO: for failure, we return the path at which it failed along with error
func validateResourceWithPattern(ctx context.EvalInterface, resource, pattern interface{}) (string, error) {
// first pass we substitute all the JMESPATH substitution for the variable
// variable: {{<JMESPATH>}}
// if a JMESPATH fails, we dont return error but variable is substitured with nil and error log
pattern = variables.SubstituteVariables(ctx, pattern)
return validateResourceElement(resource, pattern, pattern, "/")
}
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
// and calls corresponding handler
// Pattern tree and resource tree can have different structure. In this case validation fails
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) (string, error) {
var err error
switch typedPatternElement := patternElement.(type) {
// map
case map[string]interface{}:
typedResourceElement, ok := resourceElement.(map[string]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
}
return validateMap(typedResourceElement, typedPatternElement, originPattern, path)
// array
case []interface{}:
typedResourceElement, ok := resourceElement.([]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
return path, fmt.Errorf("Validation rule Failed at path %s, resource does not satisfy the expected overlay pattern", path)
}
return validateArray(typedResourceElement, typedPatternElement, originPattern, path)
// elementary values
case string, float64, int, int64, bool, nil:
/*Analyze pattern */
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String {
if isStringIsReference(checkedPattern.String()) { //check for $ anchor
patternElement, err = actualizePattern(originPattern, checkedPattern.String(), path)
if err != nil {
return path, err
}
}
}
if !ValidateValueWithPattern(resourceElement, patternElement) {
return path, fmt.Errorf("Validation rule failed at '%s' to validate value %v with pattern %v", path, resourceElement, patternElement)
}
default:
glog.V(4).Infof("Pattern contains unknown type %T. Path: %s", patternElement, path)
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
}
return "", nil
}
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) {
// check if there is anchor in pattern
// Phase 1 : Evaluate all the anchors
// Phase 2 : Evaluate non-anchors
anchors, resources := getAnchorsResourcesFromMap(patternMap)
// Evaluate anchors
for key, patternElement := range anchors {
// get handler for each pattern in the pattern
// - Conditional
// - Existance
// - Equality
handler := CreateElementHandler(key, patternElement, path)
handlerPath, err := handler.Handle(resourceMap, origPattern)
// if there are resource values at same level, then anchor acts as conditional instead of a strict check
// but if there are non then its a if then check
if err != nil {
// If Conditional anchor fails then we dont process the resources
if anchor.IsConditionAnchor(key) {
glog.V(4).Infof("condition anchor did not satisfy, wont process the resources: %s", err)
return "", nil
}
return handlerPath, err
}
}
// Evaluate resources
for key, resourceElement := range resources {
// get handler for resources in the pattern
handler := CreateElementHandler(key, resourceElement, path)
handlerPath, err := handler.Handle(resourceMap, origPattern)
if err != nil {
return handlerPath, err
}
}
return "", nil
}
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) (string, error) {
if 0 == len(patternArray) {
return path, fmt.Errorf("Pattern Array empty")
}
switch typedPatternElement := patternArray[0].(type) {
case map[string]interface{}:
// This is special case, because maps in arrays can have anchors that must be
// processed with the special way affecting the entire array
path, err := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
if err != nil {
return path, err
}
default:
// In all other cases - detect type and handle each array element with validateResourceElement
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
path, err := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
if err != nil {
return path, err
}
}
}
return "", nil
}
func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, error) {
var foundValue interface{}
referencePattern = strings.Trim(referencePattern, "$()")
operatorVariable := operator.GetOperatorFromStringPattern(referencePattern)
referencePattern = referencePattern[len(operatorVariable):]
if len(referencePattern) == 0 {
return nil, errors.New("Expected path. Found empty reference")
}
// Check for variables
// substitute it from Context
// remove abosolute path
// {{ }}
// value :=
actualPath := FormAbsolutePath(referencePattern, absolutePath)
valFromReference, err := getValueFromReference(origPattern, actualPath)
if err != nil {
return err, nil
}
//TODO validate this
if operatorVariable == operator.Equal { //if operator does not exist return raw value
return valFromReference, nil
}
foundValue, err = valFromReferenceToString(valFromReference, string(operatorVariable))
if err != nil {
return "", err
}
return string(operatorVariable) + foundValue.(string), nil
}
//Parse value to string
func valFromReferenceToString(value interface{}, operator string) (string, error) {
switch typed := value.(type) {
case string:
return typed, nil
case int, int64:
return fmt.Sprintf("%d", value), nil
case float64:
return fmt.Sprintf("%f", value), nil
default:
return "", fmt.Errorf("Incorrect expression. Operator %s does not match with value: %v", operator, value)
}
}
//FormAbsolutePath returns absolute path
func FormAbsolutePath(referencePath, absolutePath string) string {
if filepath.IsAbs(referencePath) {
return referencePath
}
return filepath.Join(absolutePath, referencePath)
}
//Prepares original pattern, path to value, and call traverse function
func getValueFromReference(origPattern interface{}, reference string) (interface{}, error) {
originalPatternMap := origPattern.(map[string]interface{})
reference = reference[1:len(reference)]
statements := strings.Split(reference, "/")
return getValueFromPattern(originalPatternMap, statements, 0)
}
func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, error) {
for key, pattern := range patternMap {
rawKey := getRawKeyIfWrappedWithAttributes(key)
if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 {
return pattern, nil
} else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 {
continue
}
switch typedPattern := pattern.(type) {
case []interface{}:
if keys[currentKeyIndex] == rawKey {
for i, value := range typedPattern {
resourceMap, ok := value.(map[string]interface{})
if !ok {
glog.V(4).Infof("Pattern and resource have different structures. Expected %T, found %T", pattern, value)
return nil, fmt.Errorf("Validation rule failed, resource does not have expected pattern %v", patternMap)
}
if keys[currentKeyIndex+1] == strconv.Itoa(i) {
return getValueFromPattern(resourceMap, keys, currentKeyIndex+2)
}
return nil, errors.New("Reference to non-existent place in the document")
}
}
return nil, errors.New("Reference to non-existent place in the document")
case map[string]interface{}:
if keys[currentKeyIndex] == rawKey {
return getValueFromPattern(typedPattern, keys, currentKeyIndex+1)
}
return nil, errors.New("Reference to non-existent place in the document")
case string, float64, int, int64, bool, nil:
continue
}
}
path := ""
/*for i := len(keys) - 1; i >= 0; i-- {
path = keys[i] + path + "/"
}*/
for _, elem := range keys {
path = "/" + elem + path
}
return nil, fmt.Errorf("No value found for specified reference: %s", path)
}
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
for i, resourceElement := range resourceMapArray {
// check the types of resource element
// expect it to be map, but can be anything ?:(
currentPath := path + strconv.Itoa(i) + "/"
returnpath, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
if err != nil {
return returnpath, err
}
}
return "", nil
return response.RuleResponse{}
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,8 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/response"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/policystore"
"github.com/nirmata/kyverno/pkg/utils"
@ -85,7 +87,7 @@ func buildKey(policy, pv, kind, ns, name, rv string) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}
func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []engine.EngineResponse {
func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []response.EngineResponse {
// convert to unstructured
unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&namespace)
if err != nil {
@ -99,7 +101,7 @@ func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []e
// get all the policies that have a generate rule and resource description satifies the namespace
// apply policy on resource
policies := listpolicies(ns, nsc.pMetaStore)
var engineResponses []engine.EngineResponse
var engineResponses []response.EngineResponse
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()) {
@ -185,10 +187,10 @@ func listpolicies(ns unstructured.Unstructured, pMetaStore policystore.LookupInt
return filteredpolicies
}
func applyPolicy(client *client.Client, resource unstructured.Unstructured, p kyverno.ClusterPolicy, policyStatus policyctr.PolicyStatusInterface) engine.EngineResponse {
func applyPolicy(client *client.Client, resource unstructured.Unstructured, p kyverno.ClusterPolicy, policyStatus policyctr.PolicyStatusInterface) response.EngineResponse {
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
gatherStat := func(policyName string, policyResponse response.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.GenerationExecutionTime = policyResponse.ProcessingTime
@ -221,10 +223,15 @@ 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))
}()
// build context
ctx := context.NewContext()
ctx.Add("resource", transformResource(resource))
policyContext := engine.PolicyContext{
NewResource: resource,
Policy: p,
Client: client,
Context: ctx,
}
engineResponse := engine.Generate(policyContext)
// gather stats
@ -234,3 +241,12 @@ func applyPolicy(client *client.Client, resource unstructured.Unstructured, p ky
return engineResponse
}
func transformResource(resource unstructured.Unstructured) []byte {
data, err := resource.MarshalJSON()
if err != nil {
glog.Errorf("failed to marshall resource %v: %v", resource, err)
return nil
}
return data
}

View file

@ -5,12 +5,12 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policyviolation"
)
func (nsc *NamespaceController) report(engineResponses []engine.EngineResponse) {
func (nsc *NamespaceController) report(engineResponses []response.EngineResponse) {
// generate events
eventInfos := generateEvents(engineResponses)
nsc.eventGen.Add(eventInfos...)
@ -19,7 +19,7 @@ func (nsc *NamespaceController) report(engineResponses []engine.EngineResponse)
nsc.pvGenerator.Add(pvInfos...)
}
func generatePVs(ers []engine.EngineResponse) []policyviolation.Info {
func generatePVs(ers []response.EngineResponse) []policyviolation.Info {
var pvInfos []policyviolation.Info
for _, er := range ers {
// ignore creation of PV for resoruces that are yet to be assigned a name
@ -38,7 +38,7 @@ func generatePVs(ers []engine.EngineResponse) []policyviolation.Info {
return pvInfos
}
func buildPVInfo(er engine.EngineResponse) policyviolation.Info {
func buildPVInfo(er response.EngineResponse) policyviolation.Info {
info := policyviolation.Info{
Blocked: false,
PolicyName: er.PolicyResponse.Policy,
@ -48,7 +48,7 @@ func buildPVInfo(er engine.EngineResponse) policyviolation.Info {
return info
}
func buildViolatedRules(er engine.EngineResponse) []kyverno.ViolatedRule {
func buildViolatedRules(er response.EngineResponse) []kyverno.ViolatedRule {
var violatedRules []kyverno.ViolatedRule
for _, rule := range er.PolicyResponse.Rules {
if rule.Success {
@ -64,7 +64,7 @@ func buildViolatedRules(er engine.EngineResponse) []kyverno.ViolatedRule {
return violatedRules
}
func generateEvents(ers []engine.EngineResponse) []event.Info {
func generateEvents(ers []response.EngineResponse) []event.Info {
var eventInfos []event.Info
for _, er := range ers {
if er.IsSuccesful() {
@ -75,7 +75,7 @@ func generateEvents(ers []engine.EngineResponse) []event.Info {
return eventInfos
}
func generateEventsPerEr(er engine.EngineResponse) []event.Info {
func generateEventsPerEr(er response.EngineResponse) []event.Info {
var eventInfos []event.Info
glog.V(4).Infof("reporting results for policy '%s' application on resource '%s/%s/%s'", er.PolicyResponse.Policy, er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
for _, rule := range er.PolicyResponse.Rules {

View file

@ -9,13 +9,14 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"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.ClusterPolicy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (responses []engine.EngineResponse) {
func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (responses []response.EngineResponse) {
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)
@ -24,7 +25,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
}()
// gather stats from the engine response
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
gatherStat := func(policyName string, policyResponse response.PolicyResponse) {
ps := PolicyStat{}
ps.PolicyName = policyName
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
@ -54,8 +55,8 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
policyStatus.SendStat(stat)
}
}
var engineResponses []engine.EngineResponse
var engineResponse engine.EngineResponse
var engineResponses []response.EngineResponse
var engineResponse response.EngineResponse
var err error
//MUTATION
@ -79,7 +80,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
//TODO: GENERATION
return engineResponses
}
func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (engine.EngineResponse, error) {
func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (response.EngineResponse, error) {
engineResponse := engine.Mutate(engine.PolicyContext{Policy: policy, NewResource: resource})
if !engineResponse.IsSuccesful() {
glog.V(4).Infof("mutation had errors reporting them")
@ -95,11 +96,11 @@ func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured,
}
// getFailedOverallRuleInfo gets detailed info for over-all mutation failure
func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse engine.EngineResponse) (engine.EngineResponse, error) {
func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse response.EngineResponse) (response.EngineResponse, error) {
rawResource, err := resource.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource: %v\n", err)
return engine.EngineResponse{}, err
return response.EngineResponse{}, err
}
// resource does not match so there was a mutation rule violated
@ -112,14 +113,14 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
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.EngineResponse{}, err
return response.EngineResponse{}, err
}
// 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.EngineResponse{}, err
return response.EngineResponse{}, err
}
if !jsonpatch.Equal(patchedResource, rawResource) {

View file

@ -8,13 +8,13 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/policyviolation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
func (pc *PolicyController) cleanUpPolicyViolation(pResponse engine.PolicyResponse) {
func (pc *PolicyController) cleanUpPolicyViolation(pResponse response.PolicyResponse) {
// 1- check if there is violation on resource (label:Selector)
// 2- check if there is violation on owner
// - recursively get owner by queries the api server for owner information of the resource

View file

@ -10,18 +10,18 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
)
func (pc *PolicyController) processExistingResources(policy kyverno.ClusterPolicy) []engine.EngineResponse {
func (pc *PolicyController) processExistingResources(policy kyverno.ClusterPolicy) []response.EngineResponse {
// Parse through all the resources
// drops the cache after configured rebuild time
pc.rm.Drop()
var engineResponses []engine.EngineResponse
var engineResponses []response.EngineResponse
// get resource that are satisfy the resource description defined in the rules
resourceMap := listResources(pc.client, policy, pc.configHandler)
for _, resource := range resourceMap {

View file

@ -5,7 +5,7 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policyviolation"
)
@ -13,7 +13,7 @@ import (
// for each policy-resource response
// - has violation -> report
// - no violation -> cleanup policy violations(resource or resource owner)
func (pc *PolicyController) cleanupAndReport(engineResponses []engine.EngineResponse) {
func (pc *PolicyController) cleanupAndReport(engineResponses []response.EngineResponse) {
// generate Events
eventInfos := generateEvents(engineResponses)
pc.eventGen.Add(eventInfos...)
@ -26,7 +26,7 @@ func (pc *PolicyController) cleanupAndReport(engineResponses []engine.EngineResp
pc.cleanUp(engineResponses)
}
func (pc *PolicyController) cleanUp(ers []engine.EngineResponse) {
func (pc *PolicyController) cleanUp(ers []response.EngineResponse) {
for _, er := range ers {
if !er.IsSuccesful() {
continue
@ -39,7 +39,7 @@ func (pc *PolicyController) cleanUp(ers []engine.EngineResponse) {
}
}
func generatePVs(ers []engine.EngineResponse) []policyviolation.Info {
func generatePVs(ers []response.EngineResponse) []policyviolation.Info {
var pvInfos []policyviolation.Info
for _, er := range ers {
// ignore creation of PV for resoruces that are yet to be assigned a name
@ -58,7 +58,7 @@ func generatePVs(ers []engine.EngineResponse) []policyviolation.Info {
return pvInfos
}
func buildPVInfo(er engine.EngineResponse) policyviolation.Info {
func buildPVInfo(er response.EngineResponse) policyviolation.Info {
info := policyviolation.Info{
Blocked: false,
PolicyName: er.PolicyResponse.Policy,
@ -68,7 +68,7 @@ func buildPVInfo(er engine.EngineResponse) policyviolation.Info {
return info
}
func buildViolatedRules(er engine.EngineResponse) []kyverno.ViolatedRule {
func buildViolatedRules(er response.EngineResponse) []kyverno.ViolatedRule {
var violatedRules []kyverno.ViolatedRule
for _, rule := range er.PolicyResponse.Rules {
if rule.Success {
@ -84,7 +84,7 @@ func buildViolatedRules(er engine.EngineResponse) []kyverno.ViolatedRule {
return violatedRules
}
func generateEvents(ers []engine.EngineResponse) []event.Info {
func generateEvents(ers []response.EngineResponse) []event.Info {
var eventInfos []event.Info
for _, er := range ers {
if er.IsSuccesful() {
@ -95,7 +95,7 @@ func generateEvents(ers []engine.EngineResponse) []event.Info {
return eventInfos
}
func generateEventsPerEr(er engine.EngineResponse) []event.Info {
func generateEventsPerEr(er response.EngineResponse) []event.Info {
var eventInfos []event.Info
glog.V(4).Infof("reporting results for policy '%s' application on resource '%s/%s/%s'", er.PolicyResponse.Policy, er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
for _, rule := range er.PolicyResponse.Rules {

View file

@ -14,6 +14,7 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -51,19 +52,19 @@ 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"`
PolicyResponse response.PolicyResponse `yaml:"policyresponse"`
}
type sValidation struct {
// expected respone from the policy engine
PolicyResponse engine.PolicyResponse `yaml:"policyresponse"`
PolicyResponse response.PolicyResponse `yaml:"policyresponse"`
}
type sGeneration struct {
// generated resources
GeneratedResources []kyverno.ResourceSpec `yaml:"generatedResources"`
// expected respone from the policy engine
PolicyResponse engine.PolicyResponse `yaml:"policyresponse"`
PolicyResponse response.PolicyResponse `yaml:"policyresponse"`
}
//getRelativePath expects a path relative to project and builds the complete path
@ -146,7 +147,7 @@ func runTestCase(t *testing.T, tc scaseT) bool {
t.FailNow()
}
var er engine.EngineResponse
var er response.EngineResponse
er = engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
t.Log("---Mutation---")
@ -229,8 +230,8 @@ func validateResource(t *testing.T, responseResource unstructured.Unstructured,
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{})) {
func validateResponse(t *testing.T, er response.PolicyResponse, expected response.PolicyResponse) {
if reflect.DeepEqual(expected, (response.PolicyResponse{})) {
t.Log("no response expected")
return
}
@ -262,7 +263,7 @@ func validateResponse(t *testing.T, er engine.PolicyResponse, expected engine.Po
}
}
func compareResourceSpec(t *testing.T, resource engine.ResourceSpec, expectedResource engine.ResourceSpec) {
func compareResourceSpec(t *testing.T, resource response.ResourceSpec, expectedResource response.ResourceSpec) {
// kind
if resource.Kind != expectedResource.Kind {
t.Errorf("kind: expected %s, recieved %s", expectedResource.Kind, resource.Kind)
@ -282,7 +283,7 @@ func compareResourceSpec(t *testing.T, resource engine.ResourceSpec, expectedRes
}
}
func compareRules(t *testing.T, rule engine.RuleResponse, expectedRule engine.RuleResponse) {
func compareRules(t *testing.T, rule response.RuleResponse, expectedRule response.RuleResponse) {
// name
if rule.Name != expectedRule.Name {
t.Errorf("rule name: expected %s, recieved %+v", expectedRule.Name, rule.Name)

View file

@ -5,7 +5,7 @@ import (
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
)
const (
@ -23,13 +23,13 @@ type rulePatch struct {
Path string `json:"path"`
}
type response struct {
type annresponse struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
func generateAnnotationPatches(engineResponses []response.EngineResponse) []byte {
var annotations map[string]string
for _, er := range engineResponses {
@ -43,7 +43,7 @@ func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
annotations = make(map[string]string)
}
var patchResponse response
var patchResponse annresponse
value := annotationFromEngineResponses(engineResponses)
if value == nil {
// no patches or error while processing patches
@ -52,7 +52,7 @@ func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
if _, ok := annotations[policyAnnotation]; ok {
// create update patch string
patchResponse = response{
patchResponse = annresponse{
Op: "replace",
Path: "/metadata/annotations/" + policyAnnotation,
Value: string(value),
@ -60,7 +60,7 @@ func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
} else {
// mutate rule has annotation patches
if len(annotations) > 0 {
patchResponse = response{
patchResponse = annresponse{
Op: "add",
Path: "/metadata/annotations/" + policyAnnotation,
Value: string(value),
@ -68,7 +68,7 @@ func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
} else {
// insert 'policies.kyverno.patches' entry in annotation map
annotations[policyAnnotation] = string(value)
patchResponse = response{
patchResponse = annresponse{
Op: "add",
Path: "/metadata/annotations",
Value: annotations,
@ -87,7 +87,7 @@ func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
return patchByte
}
func annotationFromEngineResponses(engineResponses []engine.EngineResponse) []byte {
func annotationFromEngineResponses(engineResponses []response.EngineResponse) []byte {
var policyPatches []policyPatch
for _, engineResponse := range engineResponses {
if !engineResponse.IsSuccesful() {
@ -117,7 +117,7 @@ func annotationFromEngineResponses(engineResponses []engine.EngineResponse) []by
return result
}
func annotationFromPolicyResponse(policyResponse engine.PolicyResponse) []rulePatch {
func annotationFromPolicyResponse(policyResponse response.PolicyResponse) []rulePatch {
var rulePatches []rulePatch
for _, ruleInfo := range policyResponse.Rules {
for _, patch := range ruleInfo.Patches {

View file

@ -3,21 +3,21 @@ package webhooks
import (
"testing"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func newPolicyResponse(policy, rule string, patchesStr []string, success bool) engine.PolicyResponse {
func newPolicyResponse(policy, rule string, patchesStr []string, success bool) response.PolicyResponse {
var patches [][]byte
for _, p := range patchesStr {
patches = append(patches, []byte(p))
}
return engine.PolicyResponse{
return response.PolicyResponse{
Policy: policy,
Rules: []engine.RuleResponse{
engine.RuleResponse{
Rules: []response.RuleResponse{
response.RuleResponse{
Name: rule,
Patches: patches,
Success: success},
@ -25,8 +25,8 @@ func newPolicyResponse(policy, rule string, patchesStr []string, success bool) e
}
}
func newEngineResponse(policy, rule string, patchesStr []string, success bool, annotation map[string]string) engine.EngineResponse {
return engine.EngineResponse{
func newEngineResponse(policy, rule string, patchesStr []string, success bool, annotation map[string]string) response.EngineResponse {
return response.EngineResponse{
PatchedResource: unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
@ -42,7 +42,7 @@ func Test_empty_annotation(t *testing.T) {
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, nil)
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
assert.Assert(t, string(annPatches) == expectedPatches)
}
@ -54,7 +54,7 @@ func Test_exist_annotation(t *testing.T) {
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, annotation)
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
assert.Assert(t, string(annPatches) == expectedPatches)
@ -67,7 +67,7 @@ func Test_exist_kyverno_annotation(t *testing.T) {
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, annotation)
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
assert.Assert(t, string(annPatches) == expectedPatches)
@ -79,11 +79,11 @@ func Test_annotation_nil_patch(t *testing.T) {
}
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, true, annotation)
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
assert.Assert(t, annPatches == nil)
engineResponseNew := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{""}, true, annotation)
annPatchesNew := generateAnnotationPatches([]engine.EngineResponse{engineResponseNew})
annPatchesNew := generateAnnotationPatches([]response.EngineResponse{engineResponseNew})
assert.Assert(t, annPatchesNew == nil)
}
@ -93,7 +93,7 @@ func Test_annotation_failed_Patch(t *testing.T) {
}
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, false, annotation)
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
assert.Assert(t, annPatches == nil)
}

View file

@ -4,6 +4,7 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -19,7 +20,7 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, polic
var policyStats []policyctr.PolicyStat
// gather stats from the engine response
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
gatherStat := func(policyName string, policyResponse response.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.MutationExecutionTime = policyResponse.ProcessingTime
@ -60,7 +61,7 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, polic
// 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})
resource.SetNamespace(request.Namespace)
var engineResponses []engine.EngineResponse
var engineResponses []response.EngineResponse
policyContext := engine.PolicyContext{
NewResource: *resource,
AdmissionInfo: engine.RequestInfo{

View file

@ -5,7 +5,7 @@ import (
"strings"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/policyviolation"
"github.com/golang/glog"
@ -13,7 +13,7 @@ import (
)
//generateEvents generates event info for the engine responses
func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []event.Info {
func generateEvents(engineResponses []response.EngineResponse, onUpdate bool) []event.Info {
var events []event.Info
if !isResponseSuccesful(engineResponses) {
for _, er := range engineResponses {
@ -102,7 +102,7 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev
return events
}
func generatePV(ers []engine.EngineResponse, blocked bool) []policyviolation.Info {
func generatePV(ers []response.EngineResponse, blocked bool) []policyviolation.Info {
var pvInfos []policyviolation.Info
// generate PV for each
for _, er := range ers {
@ -117,7 +117,7 @@ func generatePV(ers []engine.EngineResponse, blocked bool) []policyviolation.Inf
return pvInfos
}
func buildPVInfo(er engine.EngineResponse, blocked bool) policyviolation.Info {
func buildPVInfo(er response.EngineResponse, blocked bool) policyviolation.Info {
info := policyviolation.Info{
Blocked: blocked,
PolicyName: er.PolicyResponse.Policy,
@ -127,7 +127,7 @@ func buildPVInfo(er engine.EngineResponse, blocked bool) policyviolation.Info {
return info
}
func buildViolatedRules(er engine.EngineResponse, blocked bool) []kyverno.ViolatedRule {
func buildViolatedRules(er response.EngineResponse, blocked bool) []kyverno.ViolatedRule {
blockMsg := fmt.Sprintf("Request Blocked for resource %s/%s; ", er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Kind)
var violatedRules []kyverno.ViolatedRule
// if resource was blocked we create dependent

View file

@ -7,12 +7,13 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/response"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func isResponseSuccesful(engineReponses []engine.EngineResponse) bool {
func isResponseSuccesful(engineReponses []response.EngineResponse) bool {
for _, er := range engineReponses {
if !er.IsSuccesful() {
return false
@ -23,7 +24,7 @@ func isResponseSuccesful(engineReponses []engine.EngineResponse) bool {
// returns true -> if there is even one policy that blocks resource request
// returns false -> if all the policies are meant to report only, we dont block resource request
func toBlockResource(engineReponses []engine.EngineResponse) bool {
func toBlockResource(engineReponses []response.EngineResponse) bool {
for _, er := range engineReponses {
if er.PolicyResponse.ValidationFailureAction == Enforce {
glog.V(4).Infof("ValidationFailureAction set to enforce for policy %s , blocking resource request ", er.PolicyResponse.Policy)
@ -34,7 +35,7 @@ func toBlockResource(engineReponses []engine.EngineResponse) bool {
return false
}
func getErrorMsg(engineReponses []engine.EngineResponse) string {
func getErrorMsg(engineReponses []response.EngineResponse) string {
var str []string
var resourceInfo string

View file

@ -1,19 +1,22 @@
package webhooks
import (
"encoding/json"
"reflect"
"time"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/response"
policyctr "github.com/nirmata/kyverno/pkg/policy"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
authenticationv1 "k8s.io/api/authentication/v1"
)
// handleValidation handles validating webhook admission request
// HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules
// patchedResource is the (resource + patches) after applying mutation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, policies []kyverno.ClusterPolicy, patchedResource []byte, roles, clusterRoles []string) (bool, string) {
@ -23,7 +26,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol
var policyStats []policyctr.PolicyStat
evalTime := time.Now()
// gather stats from the engine response
gatherStat := func(policyName string, policyResponse engine.PolicyResponse) {
gatherStat := func(policyName string, policyResponse response.PolicyResponse) {
ps := policyctr.PolicyStat{}
ps.PolicyName = policyName
ps.Stats.ValidationExecutionTime = policyResponse.ProcessingTime
@ -62,7 +65,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol
ctx := context.NewContext()
// load incoming resource into the context
ctx.Add("resource", request.Object.Raw)
ctx.Add("user", transformUser(request.UserInfo))
policyContext := engine.PolicyContext{
NewResource: newR,
OldResource: oldR,
@ -72,13 +75,13 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol
ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo},
}
var engineResponses []engine.EngineResponse
var engineResponses []response.EngineResponse
for _, policy := range policies {
glog.V(2).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
newR.GetKind(), newR.GetNamespace(), newR.GetName(), request.UID, request.Operation)
policyContext.Policy = policy
engineResponse := engine.Validate(policyContext)
if reflect.DeepEqual(engineResponse, engine.EngineResponse{}) {
if reflect.DeepEqual(engineResponse, response.EngineResponse{}) {
// we get an empty response if old and new resources created the same response
// allow updates if resource update doesnt change the policy evaluation
continue
@ -124,3 +127,12 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol
glog.V(4).Infof("report: %v %s/%s/%s", time.Since(reportTime), request.Kind, request.Namespace, request.Name)
return true, ""
}
func transformUser(userInfo authenticationv1.UserInfo) []byte {
data, err := json.Marshal(userInfo)
if err != nil {
glog.Errorf("failed to marshall resource %v: %v", userInfo, err)
return nil
}
return data
}