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:
parent
507c43ddca
commit
b5de11fc0e
34 changed files with 2220 additions and 2068 deletions
pkg
engine
anchor
context
generate
generation.gomutation.gooverlay.gooverlayCondition.gopatches.goresponse
utils.goutils_test.govalidate
validation.govalidation_test.gonamespace
policy
testrunner
webhooks
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
130
pkg/engine/generate/generation.go
Normal file
130
pkg/engine/generate/generation.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package response
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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 {
|
||||
|
|
|
@ -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(`{
|
||||
|
|
52
pkg/engine/validate/common.go
Normal file
52
pkg/engine/validate/common.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package validate
|
||||
|
||||
import (
|
||||
"math"
|
|
@ -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(`
|
||||
{
|
270
pkg/engine/validate/validate.go
Normal file
270
pkg/engine/validate/validate.go
Normal 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
|
||||
}
|
1353
pkg/engine/validate/validate_test.go
Normal file
1353
pkg/engine/validate/validate_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue