diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 40abb9e07a..96197432aa 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -1,16 +1,22 @@ package engine import ( + "fmt" + "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" "github.com/nirmata/kyverno/pkg/engine/context" "github.com/nirmata/kyverno/pkg/engine/rbac" "github.com/nirmata/kyverno/pkg/engine/response" + "github.com/nirmata/kyverno/pkg/engine/utils" "github.com/nirmata/kyverno/pkg/engine/variables" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -//GenerateNew returns the list of rules that are applicable on this policy and resource +// GenerateNew +// 1. validate variables to be susbtitute in the general ruleInfo (match,exclude,condition) +// - the caller has to check the ruleResponse to determine whether the path exist +// 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed func GenerateNew(policyContext PolicyContext) (resp response.EngineResponse) { policy := policyContext.Policy resource := policyContext.NewResource @@ -55,6 +61,13 @@ func filterRules(policy kyverno.ClusterPolicy, resource unstructured.Unstructure } for _, rule := range policy.Spec.Rules { + if paths := validateGeneralRuleInfoVariables(ctx, rule); len(paths) != 0 { + glog.Infof("referenced path not present in generate rule %s, resource %s/%s/%s, path: %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), paths) + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, + newPathNotPresentRuleResponse(rule.Name, utils.Mutation.String(), fmt.Sprintf("path not present: %s", paths))) + continue + } + if ruleResp := filterRule(rule, resource, admissionInfo, ctx); ruleResp != nil { resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) } diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 4bd1262397..8d2b8a17c2 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -1,6 +1,7 @@ package engine import ( + "fmt" "reflect" "strings" "time" @@ -10,6 +11,7 @@ import ( "github.com/nirmata/kyverno/pkg/engine/mutate" "github.com/nirmata/kyverno/pkg/engine/rbac" "github.com/nirmata/kyverno/pkg/engine/response" + "github.com/nirmata/kyverno/pkg/engine/utils" "github.com/nirmata/kyverno/pkg/engine/variables" ) @@ -55,6 +57,14 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { continue } + // TODO(shuting): add unit test for validateGeneralRuleInfoVariables + if paths := validateGeneralRuleInfoVariables(ctx, rule); len(paths) != 0 { + glog.Infof("referenced path not present in rule %s, resource %s/%s/%s, path: %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), paths) + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, + newPathNotPresentRuleResponse(rule.Name, utils.Mutation.String(), fmt.Sprintf("path not present: %s", paths))) + continue + } + startTime := time.Now() if !rbac.MatchAdmissionInfo(rule, policyContext.AdmissionInfo) { glog.V(3).Infof("rule '%s' cannot be applied on %s/%s/%s, admission permission: %v", diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 2e9e2656cb..0bcd7a7716 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -9,7 +9,10 @@ import ( "github.com/minio/minio/pkg/wildcard" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/nirmata/kyverno/pkg/engine/context" "github.com/nirmata/kyverno/pkg/engine/operator" + "github.com/nirmata/kyverno/pkg/engine/response" + "github.com/nirmata/kyverno/pkg/engine/variables" "github.com/nirmata/kyverno/pkg/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -228,3 +231,33 @@ type resourceInfo struct { Resource unstructured.Unstructured Gvk *metav1.GroupVersionKind } + +// validateGeneralRuleInfoVariables validate variable subtition defined in +// - MatchResources +// - ExcludeResources +// - Conditions +func validateGeneralRuleInfoVariables(ctx context.EvalInterface, rule kyverno.Rule) string { + var invalidPaths []string + if path := variables.ValidateVariables(ctx, rule.MatchResources); len(path) != 0 { + invalidPaths = append(invalidPaths, path) + } + + if path := variables.ValidateVariables(ctx, rule.ExcludeResources); len(path) != 0 { + invalidPaths = append(invalidPaths, path) + } + + if path := variables.ValidateVariables(ctx, rule.Conditions); len(path) != 0 { + invalidPaths = append(invalidPaths, path) + } + return strings.Join(invalidPaths, ";") +} + +func newPathNotPresentRuleResponse(rname, rtype, msg string) response.RuleResponse { + return response.RuleResponse{ + Name: rname, + Type: rtype, + Message: msg, + Success: true, + PathNotPresent: true, + } +} diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 1999d0b09a..fb52ef6927 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -95,6 +95,14 @@ func validateResource(ctx context.EvalInterface, policy kyverno.ClusterPolicy, r continue } startTime := time.Now() + + if paths := validateGeneralRuleInfoVariables(ctx, rule); len(paths) != 0 { + glog.Infof("referenced path not present in rule %s/, resource %s/%s/%s, path: %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), paths) + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, + newPathNotPresentRuleResponse(rule.Name, utils.Validation.String(), fmt.Sprintf("path not present: %s", paths))) + continue + } + if !rbac.MatchAdmissionInfo(rule, admissionInfo) { glog.V(3).Infof("rule '%s' cannot be applied on %s/%s/%s, admission permission: %v", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), admissionInfo) diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go index 26c7aa186e..791b386f61 100644 --- a/pkg/generate/generate.go +++ b/pkg/generate/generate.go @@ -85,6 +85,13 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern return nil, fmt.Errorf("policy %s, dont not apply to resource %v", gr.Spec.Policy, gr.Spec.Resource) } + if pv := buildPathNotPresentPV(engineResponse); pv != nil { + c.pvGenerator.Add(pv...) + // variable substitiution fails in ruleInfo (match,exclude,condition) + // the overall policy should not apply to resource + return nil, fmt.Errorf("referenced path not present in generate policy %s", policy.Name) + } + // Apply the generate rule on resource return applyGeneratePolicy(c.client, policyContext, gr.Status.State) } @@ -132,6 +139,10 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured. var err error var noGenResource kyverno.ResourceSpec + if invalidPaths := variables.ValidateVariables(ctx, rule.Generation.ResourceSpec); len(invalidPaths) != 0 { + return noGenResource, NewViolation(rule.Name, fmt.Errorf("path not present in generate resource spec: %s", invalidPaths)) + } + // variable substitution // - name // - namespace @@ -167,7 +178,7 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured. } // CLONE if gen.Clone != (kyverno.CloneFrom{}) { - if rdata, err = handleClone(gen, client, resource, ctx, state); err != nil { + if rdata, err = handleClone(rule.Name, gen, client, resource, ctx, state); err != nil { glog.V(4).Info(err) switch e := err.(type) { case *NotFound: @@ -237,6 +248,10 @@ func variableSubsitutionForAttributes(gen kyverno.Generation, ctx context.EvalIn } func handleData(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState) (map[string]interface{}, error) { + if invalidPaths := variables.ValidateVariables(ctx, generateRule.Data); len(invalidPaths) != 0 { + return nil, NewViolation(ruleName, fmt.Errorf("path not present in generate data: %s", invalidPaths)) + } + newData := variables.SubstituteVariables(ctx, generateRule.Data) // check if resource exists @@ -278,7 +293,11 @@ func handleData(ruleName string, generateRule kyverno.Generation, client *dclien return nil, nil } -func handleClone(generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState) (map[string]interface{}, error) { +func handleClone(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState) (map[string]interface{}, error) { + if invalidPaths := variables.ValidateVariables(ctx, generateRule.Clone); len(invalidPaths) != 0 { + return nil, NewViolation(ruleName, fmt.Errorf("path not present in generate clone: %s", invalidPaths)) + } + // check if resource exists _, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name) if err == nil { diff --git a/pkg/generate/report.go b/pkg/generate/report.go index 5285272fd4..e00ed7308a 100644 --- a/pkg/generate/report.go +++ b/pkg/generate/report.go @@ -5,7 +5,9 @@ import ( "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/nirmata/kyverno/pkg/engine/response" "github.com/nirmata/kyverno/pkg/event" + "github.com/nirmata/kyverno/pkg/policyviolation" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -108,3 +110,13 @@ func successEvents(gr kyverno.GenerateRequest, resource unstructured.Unstructure return events } + +// buildPathNotPresentPV build violation info when referenced path not found +func buildPathNotPresentPV(er response.EngineResponse) []policyviolation.Info { + for _, rr := range er.PolicyResponse.Rules { + if rr.PathNotPresent { + return policyviolation.GeneratePVsFromEngineResponse([]response.EngineResponse{er}) + } + } + return nil +} diff --git a/pkg/policyviolation/builder.go b/pkg/policyviolation/builder.go index 6694923999..eefe9d45d2 100644 --- a/pkg/policyviolation/builder.go +++ b/pkg/policyviolation/builder.go @@ -81,7 +81,7 @@ func buildPVInfo(er response.EngineResponse) Info { func buildViolatedRules(er response.EngineResponse) []kyverno.ViolatedRule { var violatedRules []kyverno.ViolatedRule for _, rule := range er.PolicyResponse.Rules { - if rule.Success { + if rule.Success && !rule.PathNotPresent { continue } vrule := kyverno.ViolatedRule{