1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

refactor variable substitution

This commit is contained in:
shivkumar dudhani 2020-02-14 11:59:28 -08:00
parent d855247a98
commit 5cee543755
26 changed files with 456 additions and 998 deletions

View file

@ -30,16 +30,18 @@ type EvalInterface interface {
//Context stores the data resources as JSON
type Context struct {
mu sync.RWMutex
// data map[string]interface{}
jsonRaw []byte
mu sync.RWMutex
jsonRaw []byte
whiteListVars []string
}
//NewContext returns a new context
func NewContext() *Context {
// pass the list of variables to be white-listed
func NewContext(whiteListVars ...string) *Context {
ctx := Context{
// data: map[string]interface{}{},
jsonRaw: []byte(`{}`), // empty json struct
jsonRaw: []byte(`{}`), // empty json struct
whiteListVars: whiteListVars,
}
return &ctx
}

View file

@ -11,6 +11,11 @@ import (
//Query the JSON context with JMESPATH search path
func (ctx *Context) Query(query string) (interface{}, error) {
var emptyResult interface{}
// check for white-listed variables
if ctx.isWhiteListed(query) {
return emptyResult, fmt.Errorf("variable %s cannot be used", query)
}
// compile the query
queryPath, err := jmespath.Compile(query)
if err != nil {
@ -34,3 +39,12 @@ func (ctx *Context) Query(query string) (interface{}, error) {
}
return result, nil
}
func (ctx *Context) isWhiteListed(variable string) bool {
for _, wVar := range ctx.whiteListVars {
if wVar == variable {
return true
}
}
return false
}

View file

@ -1,14 +1,11 @@
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"
)
@ -37,6 +34,7 @@ func filterRule(rule kyverno.Rule, resource unstructured.Unstructured, admission
}
// operate on the copy of the conditions, as we perform variable substitution
copyConditions := copyConditions(rule.Conditions)
// evaluate pre-conditions
if !variables.EvaluateConditions(ctx, copyConditions) {
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
@ -62,13 +60,6 @@ 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)
}

View file

@ -14,40 +14,22 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
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/context"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/utils"
"github.com/nirmata/kyverno/pkg/engine/variables"
)
// ProcessOverlay processes mutation overlay on the resource
func ProcessOverlay(ctx context.EvalInterface, rule kyverno.Rule, resource unstructured.Unstructured) (resp response.RuleResponse, patchedResource unstructured.Unstructured) {
func ProcessOverlay(ruleName string, overlay interface{}, 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)
resp.Name = rule.Name
glog.V(4).Infof("started applying overlay rule %q (%v)", ruleName, startTime)
resp.Name = ruleName
resp.Type = utils.Mutation.String()
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying overlay rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
}()
// if referenced path not present, we skip processing the rule and report violation
if invalidPaths := variables.ValidateVariables(ctx, rule.Mutation.Overlay); len(invalidPaths) != 0 {
resp.Success = true
resp.PathNotPresent = true
resp.Message = fmt.Sprintf("referenced path not present: %s", invalidPaths)
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resp.Message)
return resp, resource
}
// substitute variables
// 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
overlay := variables.SubstituteVariables(ctx, rule.Mutation.Overlay)
patches, overlayerr := processOverlayPatches(resource.UnstructuredContent(), overlay)
// resource does not satisfy the overlay pattern, we don't apply this rule
if !reflect.DeepEqual(overlayerr, overlayError{}) {
@ -55,19 +37,19 @@ func ProcessOverlay(ctx context.EvalInterface, rule kyverno.Rule, resource unstr
// condition key is not present in the resource, don't apply this rule
// 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())
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", ruleName, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
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())
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", ruleName, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
//TODO: send zero response and not consider this as applied?
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)
glog.Errorf("Resource %s/%s/%s: failed to process overlay: %v in the rule %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg(), ruleName)
resp.Success = false
resp.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
return resp, resource

View file

@ -1,7 +1,6 @@
package engine
import (
"fmt"
"reflect"
"strings"
"time"
@ -11,7 +10,6 @@ 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"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -36,26 +34,13 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
defer endMutateResultResponse(&resp, startTime)
incrementAppliedRuleCount := func() {
// rules applied successfully count
resp.PolicyResponse.RulesAppliedCount++
}
patchedResource := policyContext.NewResource
for _, rule := range policy.Spec.Rules {
var ruleResponse response.RuleResponse
//TODO: to be checked before calling the resources as well
if !rule.HasMutate() && !strings.Contains(PodControllers, resource.GetKind()) {
continue
}
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 in rule info: %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",
@ -76,34 +61,38 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
// operate on the copy of the conditions, as we perform variable substitution
copyConditions := copyConditions(rule.Conditions)
// evaluate pre-conditions
// - handle variable subsitutions
if !variables.EvaluateConditions(ctx, copyConditions) {
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
mutation := rule.Mutation.DeepCopy()
// Process Overlay
if rule.Mutation.Overlay != nil {
var ruleResponse response.RuleResponse
ruleResponse, patchedResource = mutate.ProcessOverlay(ctx, rule, patchedResource)
if ruleResponse.Success {
// - variable substitution path is not present
if ruleResponse.PathNotPresent {
glog.V(4).Infof(ruleResponse.Message)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
continue
}
if mutation.Overlay != nil {
overlay := mutation.Overlay
// subsiitue the variables
var err error
if overlay, err = variables.SubstituteVars(ctx, overlay); err != nil {
// variable subsitution failed
ruleResponse.Success = false
ruleResponse.Message = err.Error()
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
continue
}
ruleResponse, patchedResource = mutate.ProcessOverlay(rule.Name, overlay, patchedResource)
if ruleResponse.Success {
// - overlay pattern does not match the resource conditions
if ruleResponse.Patches == nil {
glog.V(4).Infof(ruleResponse.Message)
continue
}
glog.Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
}
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
incrementAppliedRuleCount(&resp)
}
// Process Patches
@ -112,7 +101,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
ruleResponse, patchedResource = mutate.ProcessPatches(rule, patchedResource)
glog.Infof("Mutate patches in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
incrementAppliedRuleCount()
incrementAppliedRuleCount(&resp)
}
// insert annotation to podtemplate if resource is pod controller
@ -123,7 +112,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
if strings.Contains(PodControllers, resource.GetKind()) {
var ruleResponse response.RuleResponse
ruleResponse, patchedResource = mutate.ProcessOverlay(ctx, podTemplateRule, patchedResource)
ruleResponse, patchedResource = mutate.ProcessOverlay(rule.Name, podTemplateRule, patchedResource)
if !ruleResponse.Success {
glog.Errorf("Failed to insert annotation to podTemplate of %s/%s/%s: %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), ruleResponse.Message)
continue
@ -139,6 +128,9 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
resp.PatchedResource = patchedResource
return resp
}
func incrementAppliedRuleCount(resp *response.EngineResponse) {
resp.PolicyResponse.RulesAppliedCount++
}
func startMutateResultResponse(resp *response.EngineResponse, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) {
// set policy information

View file

@ -3,7 +3,6 @@ package engine
import (
"encoding/json"
"reflect"
"strings"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -152,52 +151,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
Context: ctx,
NewResource: *resourceUnstructured}
er := Mutate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent, true)
}
func Test_variableSubstitutionPathNotExist_InRuleInfo(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {
"name": "check-root-user"
}
}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "test-validate-variables"
},
"spec": {
"rules": [
{
"name": "test-match",
"match": {
"resources": {
"kinds": [
"{{request.kind}}"
]
}
}
}
]
}
}`)
var policy kyverno.ClusterPolicy
assert.NilError(t, json.Unmarshal(policyraw, &policy))
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
ctx.AddResource(resourceRaw)
policyContext := PolicyContext{
Policy: policy,
Context: ctx,
NewResource: *resourceUnstructured}
er := Mutate(policyContext)
assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "path not present in rule info"))
expectedErrorStr := "variable(s) not found or has nil values: [/spec/name/{{request.object.metadata.name1}}]"
t.Log(er.PolicyResponse.Rules[0].Message)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr)
}

View file

@ -4,19 +4,21 @@ import (
"fmt"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/variables"
)
//ContainsUserInfo returns error is userInfo is defined
func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
var err error
// iterate of the policy rules to identify if userInfo is used
for idx, rule := range policy.Spec.Rules {
if err := userInfoDefined(rule.MatchResources.UserInfo); err != nil {
return fmt.Errorf("path: spec/rules[%d]/match/%s", idx, err)
if path := userInfoDefined(rule.MatchResources.UserInfo); path != "" {
return fmt.Errorf("userInfo variable used at path: spec/rules[%d]/match/%s", idx, path)
}
if err := userInfoDefined(rule.ExcludeResources.UserInfo); err != nil {
return fmt.Errorf("path: spec/rules[%d]/exclude/%s", idx, err)
if path := userInfoDefined(rule.ExcludeResources.UserInfo); path != "" {
return fmt.Errorf("userInfo variable used at path: spec/rules[%d]/exclude/%s", idx, path)
}
// variable defined with user information
@ -29,40 +31,43 @@ func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
// - request.userInfo*
// - serviceAccountName
// - serviceAccountNamespace
filterVars := []string{"request.userInfo*", "serviceAccountName", "serviceAccountNamespace"}
ctx := context.NewContext(filterVars...)
for condIdx, condition := range rule.Conditions {
if err := variables.CheckVariables(condition.Key, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/condition[%d]/key%s", idx, condIdx, err)
if condition.Key, err = variables.SubstituteVars(ctx, condition.Key); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/condition[%d]/key", idx, condIdx)
}
if err := variables.CheckVariables(condition.Value, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/condition[%d]/value%s", idx, condIdx, err)
if condition.Value, err = variables.SubstituteVars(ctx, condition.Value); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/condition[%d]/value", idx, condIdx)
}
}
if err := variables.CheckVariables(rule.Mutation.Overlay, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/mutate/overlay%s", idx, err)
if rule.Mutation.Overlay, err = variables.SubstituteVars(ctx, rule.Mutation.Overlay); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/mutate/overlay", idx)
}
if err := variables.CheckVariables(rule.Validation.Pattern, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/validate/pattern%s", idx, err)
if rule.Validation.Pattern, err = variables.SubstituteVars(ctx, rule.Validation.Pattern); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/validate/pattern", idx)
}
for idx2, pattern := range rule.Validation.AnyPattern {
if err := variables.CheckVariables(pattern, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/validate/anyPattern[%d]%s", idx, idx2, err)
if pattern, err = variables.SubstituteVars(ctx, pattern); err != nil {
return fmt.Errorf("userInfo variable used at spec/rules[%d]/validate/anyPattern[%d]", idx, idx2)
}
}
}
return nil
}
func userInfoDefined(ui kyverno.UserInfo) error {
func userInfoDefined(ui kyverno.UserInfo) string {
if len(ui.Roles) > 0 {
return fmt.Errorf("roles")
return "roles"
}
if len(ui.ClusterRoles) > 0 {
return fmt.Errorf("clusterRoles")
return "clusterRoles"
}
if len(ui.Subjects) > 0 {
return fmt.Errorf("subjects")
return "subjects"
}
return nil
return ""
}

View file

@ -30,7 +30,7 @@ func Validate(p kyverno.ClusterPolicy) error {
// policy.spec.background -> "true"
// - cannot use variables with request.userInfo
// - cannot define userInfo(roles, cluserRoles, subjects) for filtering (match & exclude)
return fmt.Errorf("userInfo not allowed in background policy mode. Failure path %s", err)
return fmt.Errorf("userInfo not allowed in background policy mode. %v", err)
}
}

View file

@ -1190,10 +1190,7 @@ func Test_BackGroundUserInfo_match_roles(t *testing.T) {
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/match/roles" {
t.Error("Incorrect Path")
}
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/roles")
}
func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
@ -1226,10 +1223,7 @@ func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/match/clusterRoles" {
t.Log(err)
t.Error("Incorrect Path")
}
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/clusterRoles")
}
func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
@ -1265,10 +1259,7 @@ func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/match/subjects" {
t.Log(err)
t.Error("Incorrect Path")
}
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/subjects")
}
func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
@ -1300,7 +1291,7 @@ func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/mutate/overlay/var1/{{request.userInfo}}" {
if err.Error() != "userInfo variable used at spec/rules[0]/mutate/overlay" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -1335,7 +1326,7 @@ func Test_BackGroundUserInfo_mutate_overlay2(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/mutate/overlay/var1/{{request.userInfo.userName}}" {
if err.Error() != "userInfo variable used at spec/rules[0]/mutate/overlay" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -1370,7 +1361,7 @@ func Test_BackGroundUserInfo_validate_pattern(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/validate/pattern/var1/{{request.userInfo}}" {
if err.Error() != "userInfo variable used at spec/rules[0]/validate/pattern" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -1409,7 +1400,7 @@ func Test_BackGroundUserInfo_validate_anyPattern(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{request.userInfo}}" {
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -1448,7 +1439,7 @@ func Test_BackGroundUserInfo_validate_anyPattern_multiple_var(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{request.userInfo}}-{{temp}}" {
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
t.Log(err)
t.Error("Incorrect Path")
}
@ -1487,7 +1478,7 @@ func Test_BackGroundUserInfo_validate_anyPattern_serviceAccount(t *testing.T) {
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{serviceAccountName}}" {
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
t.Log(err)
t.Error("Incorrect Path")
}

View file

@ -9,9 +9,6 @@ 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/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"
@ -224,48 +221,10 @@ func findKind(kinds []string, kindGVK string) bool {
return false
}
// validateGeneralRuleInfoVariables validate variable subtition defined in
// - MatchResources
// - ExcludeResources
// - Conditions
func validateGeneralRuleInfoVariables(ctx context.EvalInterface, rule kyverno.Rule) string {
var tempRule kyverno.Rule
var tempRulePattern interface{}
tempRule.MatchResources = rule.MatchResources
tempRule.ExcludeResources = rule.ExcludeResources
tempRule.Conditions = rule.Conditions
raw, err := json.Marshal(tempRule)
if err != nil {
glog.Infof("failed to serilize rule info while validating variable substitution: %v", err)
return ""
}
if err := json.Unmarshal(raw, &tempRulePattern); err != nil {
glog.Infof("failed to serilize rule info while validating variable substitution: %v", err)
return ""
}
return variables.ValidateVariables(ctx, tempRulePattern)
}
func newPathNotPresentRuleResponse(rname, rtype, msg string) response.RuleResponse {
return response.RuleResponse{
Name: rname,
Type: rtype,
Message: msg,
Success: true,
PathNotPresent: true,
}
}
func copyConditions(original []kyverno.Condition) []kyverno.Condition {
var copy []kyverno.Condition
for _, condition := range original {
copyCondition := kyverno.Condition{}
condition.DeepCopyInto(&copyCondition)
copy = append(copy, copyCondition)
copy = append(copy, *condition.DeepCopy())
}
return copy
}

View file

@ -1,15 +1,11 @@
package engine
import (
"encoding/json"
"fmt"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
context "github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/utils"
"gotest.tools/assert"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -396,116 +392,3 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
assert.Assert(t, !MatchesResourceDescription(*resource, rule))
}
func Test_validateGeneralRuleInfoVariables(t *testing.T) {
rawResource := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "image-with-hostpath",
"labels": {
"app.type": "prod",
"namespace": "my-namespace"
}
},
"spec": {
"containers": [
{
"name": "image-with-hostpath",
"image": "docker.io/nautiker/curl",
"volumeMounts": [
{
"name": "var-lib-etcd",
"mountPath": "/var/lib"
}
]
}
],
"volumes": [
{
"name": "var-lib-etcd",
"emptyDir": {}
}
]
}
}
`)
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "test-validate-variables"
},
"spec": {
"rules": [
{
"name": "test-match",
"match": {
"Subjects": [
{
"kind": "User",
"name": "{{request.userInfo.username1}}}"
}
],
"resources": {
"kind": "{{request.object.kind}}"
}
}
},
{
"name": "test-exclude",
"match": {
"resources": {
"namespaces": [
"{{request.object.namespace}}"
]
}
}
},
{
"name": "test-condition",
"preconditions": [
{
"key": "{{serviceAccountName}}",
"operator": "NotEqual",
"value": "testuser"
}
]
}
]
}
}`)
userReqInfo := kyverno.RequestInfo{
AdmissionUserInfo: authenticationv1.UserInfo{
Username: "user1",
},
}
var policy kyverno.ClusterPolicy
assert.NilError(t, json.Unmarshal(policyRaw, &policy))
ctx := context.NewContext()
var err error
err = ctx.AddResource(rawResource)
if err != nil {
t.Error(err)
}
err = ctx.AddUserInfo(userReqInfo)
if err != nil {
t.Error(err)
}
err = ctx.AddSA("system:serviceaccount:test:testuser")
if err != nil {
t.Error(err)
}
expectPaths := []string{"request.userInfo.username1", "request.object.namespace", ""}
for i, rule := range policy.Spec.Rules {
invalidPaths := validateGeneralRuleInfoVariables(ctx, rule)
assert.Assert(t, invalidPaths == expectPaths[i], fmt.Sprintf("result not match, got invalidPaths %s", invalidPaths))
}
}

View file

@ -1,7 +1,6 @@
package validate
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
@ -11,36 +10,9 @@ import (
"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
func ValidateResourceWithPattern(ctx context.EvalInterface, resource, pattern interface{}) (string, ValidationError) {
// if referenced path is not present, we skip processing the rule and report violation
if invalidPaths := variables.ValidateVariables(ctx, pattern); len(invalidPaths) != 0 {
return "", newValidatePatternError(PathNotPresent, invalidPaths)
}
// Operate on copy of the value for variable substitution
patternCopy, err := copyInterface(pattern)
if err != nil {
return "", newValidatePatternError(OtherError, err.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
patternCopy = variables.SubstituteVariables(ctx, patternCopy)
path, err := validateResourceElement(resource, patternCopy, patternCopy, "/")
if err != nil {
return path, newValidatePatternError(Rulefailure, err.Error())
}
return "", ValidationError{}
}
// ValidateResourceWithPattern1 is a start of element-by-element validation process
// It assumes that validation is started from root, so "/" is passed
func ValidateResourceWithPattern1(resource, pattern interface{}) (string, error) {
@ -52,20 +24,6 @@ func ValidateResourceWithPattern1(resource, pattern interface{}) (string, error)
return "", nil
}
func copyInterface(original interface{}) (interface{}, error) {
tempData, err := json.Marshal(original)
if err != nil {
return nil, err
}
fmt.Println(string(tempData))
var temp interface{}
err = json.Unmarshal(tempData, &temp)
if err != nil {
return nil, err
}
return temp, nil
}
// 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
@ -102,7 +60,7 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte
}
}
if !ValidateValueWithPattern(resourceElement, patternElement) {
return path, fmt.Errorf("Validation rule failed at '%s' to validate value %v with pattern %v", path, resourceElement, patternElement)
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
}
default:

View file

@ -1,10 +1,8 @@
package engine
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
"github.com/golang/glog"
@ -95,13 +93,6 @@ func validateResource(ctx context.EvalInterface, policy kyverno.ClusterPolicy, r
}
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)
@ -121,6 +112,7 @@ func validateResource(ctx context.EvalInterface, policy kyverno.ClusterPolicy, r
// operate on the copy of the conditions, as we perform variable substitution
copyConditions := copyConditions(rule.Conditions)
// evaluate pre-conditions
// - handle variable subsitutions
if !variables.EvaluateConditions(ctx, copyConditions) {
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
continue
@ -184,33 +176,29 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
resp.RuleStats.ProcessingTime = time.Since(startTime)
glog.V(4).Infof("finished applying validation rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
}()
// work on a copy of validation rule
validationRule := rule.Validation.DeepCopy()
// either pattern or anyPattern can be specified in Validation rule
if rule.Validation.Pattern != nil {
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, rule.Validation.Pattern)
if !reflect.DeepEqual(err, validate.ValidationError{}) {
switch err.StatusCode {
case validate.PathNotPresent:
resp.Success = true
resp.PathNotPresent = true
resp.Message = fmt.Sprintf("referenced path not present: %s", err.ErrorMsg)
glog.V(4).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resp.Message)
case validate.Rulefailure:
// 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)
resp.Success = false
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed at path '%s'",
rule.Validation.Message, rule.Name, path)
case validate.OtherError:
// other error
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)
resp.Success = false
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed at path '%s'",
rule.Validation.Message, rule.Name, path)
}
if validationRule.Pattern != nil {
// substitute variables in the pattern
pattern := validationRule.Pattern
var err error
if pattern, err = variables.SubstituteVars(ctx, pattern); err != nil {
// variable subsitution failed
resp.Success = false
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed. '%s'",
rule.Validation.Message, rule.Name, err)
return resp
}
if path, err := validate.ValidateResourceWithPattern1(resource.Object, pattern); err != nil {
// validation failed
resp.Success = false
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed at path '%s'",
rule.Validation.Message, rule.Name, path)
return resp
}
// rule application successful
glog.V(4).Infof("rule %s pattern validated successfully on resource %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
resp.Success = true
@ -218,55 +206,45 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
return resp
}
// using anyPattern we can define multiple patterns and only one of them has to be successfully validated
// return directly if one pattern succeed
// if none succeed, report violation / policyerror(TODO)
if rule.Validation.AnyPattern != nil {
var ruleFailureErrs []error
var failedPaths, invalidPaths []string
for index, pattern := range rule.Validation.AnyPattern {
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, pattern)
// this pattern was successfully validated
if reflect.DeepEqual(err, validate.ValidationError{}) {
glog.V(4).Infof("anyPattern %v successfully validated on resource %s/%s/%s", pattern, resource.GetKind(), resource.GetNamespace(), resource.GetName())
if validationRule.AnyPattern != nil {
var failedSubstitutionsErrors []error
var failedAnyPatternsErrors []error
var err error
for idx, pattern := range validationRule.AnyPattern {
if pattern, err = variables.SubstituteVars(ctx, pattern); err != nil {
// variable subsitution failed
failedSubstitutionsErrors = append(failedSubstitutionsErrors, err)
continue
}
_, err := validate.ValidateResourceWithPattern1(resource.Object, pattern)
if err == nil {
resp.Success = true
resp.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, index)
resp.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, idx)
return resp
}
switch err.StatusCode {
case validate.PathNotPresent:
invalidPaths = append(invalidPaths, err.ErrorMsg)
case validate.Rulefailure:
glog.V(4).Infof("Validation error: %s; Validation rule %s anyPattern[%d] failed at path %s for %s/%s/%s",
rule.Validation.Message, rule.Name, index, path, resource.GetKind(), resource.GetNamespace(), resource.GetName())
ruleFailureErrs = append(ruleFailureErrs, errors.New(err.ErrorMsg))
failedPaths = append(failedPaths, path)
}
glog.V(4).Infof("Validation error: %s; Validation rule %s anyPattern[%d] for %s/%s/%s",
rule.Validation.Message, rule.Name, idx, resource.GetKind(), resource.GetNamespace(), resource.GetName())
patternErr := fmt.Errorf("anyPattern[%d] failed; %s", idx, err)
failedAnyPatternsErrors = append(failedAnyPatternsErrors, patternErr)
}
// PathNotPresent
if len(invalidPaths) != 0 {
resp.Success = true
resp.PathNotPresent = true
resp.Message = fmt.Sprintf("referenced path not present: %s", strings.Join(invalidPaths, ";"))
glog.V(4).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resp.Message)
// Subsitution falures
if len(failedSubstitutionsErrors) > 0 {
resp.Success = false
resp.Message = fmt.Sprintf("Subsitutions failed at paths: %v", failedSubstitutionsErrors)
return resp
}
// none of the anyPatterns succeed: len(ruleFailureErrs) > 0
glog.V(4).Infof("none of anyPattern comply with resource: %v", ruleFailureErrs)
resp.Success = false
var errorStr []string
for index, err := range ruleFailureErrs {
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)
// Any Pattern validation errors
if len(failedAnyPatternsErrors) > 0 {
var errorStr []string
for _, err := range failedAnyPatternsErrors {
errorStr = append(errorStr, err.Error())
}
resp.Success = false
resp.Message = fmt.Sprintf("Validation rule '%s' failed. %s", rule.Name, errorStr)
return resp
}
resp.Message = fmt.Sprintf("Validation error: %s; %s", rule.Validation.Message, strings.Join(errorStr, " "))
return resp
}
return response.RuleResponse{}
}

View file

@ -294,7 +294,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
msgs := []string{"Validation error: A namespace is required; Validation rule check-default-namespace anyPattern[0] failed at path /metadata/namespace/. Validation rule check-default-namespace anyPattern[1] failed at path /metadata/namespace/."}
msgs := []string{"Validation rule 'check-default-namespace' failed. [anyPattern[0] failed; Validation rule failed at '/metadata/namespace/' to validate value '<nil>' with pattern '?*' anyPattern[1] failed; Validation rule failed at '/metadata/namespace/' to validate value '<nil>' with pattern '!default']"}
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
}
@ -1309,8 +1309,8 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
Context: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].Success, true)
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent, true)
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation error: ; Validation rule 'test-path-not-exist' failed. 'variable(s) not found or has nil values: [/spec/containers/0/name/{{request.object.metadata.name1}}]'")
}
func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *testing.T) {
@ -1399,10 +1399,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *t
Context: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].Success == true)
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent == false)
expectMsg := "Validation rule 'test-path-not-exist' anyPattern[1] succeeded."
assert.Assert(t, er.PolicyResponse.Rules[0].Message == expectMsg)
assert.Assert(t, er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' anyPattern[1] succeeded.")
}
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) {
@ -1491,8 +1489,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
Context: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].Success, true)
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent, true)
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Subsitutions failed at paths: [variable(s) not found or has nil values: [/spec/template/spec/containers/0/name/{{request.object.metadata.name1}}] variable(s) not found or has nil values: [/spec/template/spec/containers/0/name/{{request.object.metadata.name2}}]]")
}
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
@ -1582,8 +1580,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
NewResource: *resourceUnstructured}
er := Validate(policyContext)
expectedMsg := "Validation error: ; Validation rule test-path-not-exist anyPattern[0] failed at path /spec/template/spec/containers/0/name/. Validation rule test-path-not-exist anyPattern[1] failed at path /spec/template/spec/containers/0/name/."
assert.Assert(t, er.PolicyResponse.Rules[0].Success == false)
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent == false)
assert.Assert(t, er.PolicyResponse.Rules[0].Message == expectedMsg)
// expectedMsg := "Validation error: ; Validation rule test-path-not-exist anyPattern[0] failed at path /spec/template/spec/containers/0/name/. Validation rule test-path-not-exist anyPattern[1] failed at path /spec/template/spec/containers/0/name/."
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' failed. [anyPattern[0] failed; Validation rule failed at '/spec/template/spec/containers/0/name/' to validate value 'pod-test-pod' with pattern 'test*' anyPattern[1] failed; Validation rule failed at '/spec/template/spec/containers/0/name/' to validate value 'pod-test-pod' with pattern 'test*']")
}

View file

@ -10,7 +10,7 @@ import (
//Evaluate evaluates the condition
func Evaluate(ctx context.EvalInterface, condition kyverno.Condition) bool {
// get handler for the operator
handle := operator.CreateOperatorHandler(ctx, condition.Operator, SubstituteVariables)
handle := operator.CreateOperatorHandler(ctx, condition.Operator, SubstituteVars)
if handle == nil {
return false
}

View file

@ -25,25 +25,36 @@ type EqualHandler struct {
//Evaluate evaluates expression with Equal Operator
func (eh EqualHandler) Evaluate(key, value interface{}) bool {
var err error
//TODO: decouple variables from evaluation
// substitute the variables
nKey := eh.subHandler(eh.ctx, key)
nValue := eh.subHandler(eh.ctx, value)
if key, err = eh.subHandler(eh.ctx, key); err != nil {
// Failed to resolve the variable
glog.Infof("Failed to resolve variables in key: %s: %v", key, err)
return false
}
if value, err = eh.subHandler(eh.ctx, value); err != nil {
// Failed to resolve the variable
glog.Infof("Failed to resolve variables in value: %s: %v", value, err)
return false
}
// key and value need to be of same type
switch typedKey := nKey.(type) {
switch typedKey := key.(type) {
case bool:
return eh.validateValuewithBoolPattern(typedKey, nValue)
return eh.validateValuewithBoolPattern(typedKey, value)
case int:
return eh.validateValuewithIntPattern(int64(typedKey), nValue)
return eh.validateValuewithIntPattern(int64(typedKey), value)
case int64:
return eh.validateValuewithIntPattern(typedKey, nValue)
return eh.validateValuewithIntPattern(typedKey, value)
case float64:
return eh.validateValuewithFloatPattern(typedKey, nValue)
return eh.validateValuewithFloatPattern(typedKey, value)
case string:
return eh.validateValuewithStringPattern(typedKey, nValue)
return eh.validateValuewithStringPattern(typedKey, value)
case map[string]interface{}:
return eh.validateValueWithMapPattern(typedKey, nValue)
return eh.validateValueWithMapPattern(typedKey, value)
case []interface{}:
return eh.validateValueWithSlicePattern(typedKey, nValue)
return eh.validateValueWithSlicePattern(typedKey, value)
default:
glog.Errorf("Unsupported type %v", typedKey)
return false

View file

@ -25,25 +25,35 @@ type NotEqualHandler struct {
//Evaluate evaluates expression with NotEqual Operator
func (neh NotEqualHandler) Evaluate(key, value interface{}) bool {
var err error
//TODO: decouple variables from evaluation
// substitute the variables
nKey := neh.subHandler(neh.ctx, key)
nValue := neh.subHandler(neh.ctx, value)
if key, err = neh.subHandler(neh.ctx, key); err != nil {
// Failed to resolve the variable
glog.Infof("Failed to resolve variables in key: %s: %v", key, err)
return false
}
if value, err = neh.subHandler(neh.ctx, value); err != nil {
// Failed to resolve the variable
glog.Infof("Failed to resolve variables in value: %s: %v", value, err)
return false
}
// key and value need to be of same type
switch typedKey := nKey.(type) {
switch typedKey := key.(type) {
case bool:
return neh.validateValuewithBoolPattern(typedKey, nValue)
return neh.validateValuewithBoolPattern(typedKey, value)
case int:
return neh.validateValuewithIntPattern(int64(typedKey), nValue)
return neh.validateValuewithIntPattern(int64(typedKey), value)
case int64:
return neh.validateValuewithIntPattern(typedKey, nValue)
return neh.validateValuewithIntPattern(typedKey, value)
case float64:
return neh.validateValuewithFloatPattern(typedKey, nValue)
return neh.validateValuewithFloatPattern(typedKey, value)
case string:
return neh.validateValuewithStringPattern(typedKey, nValue)
return neh.validateValuewithStringPattern(typedKey, value)
case map[string]interface{}:
return neh.validateValueWithMapPattern(typedKey, nValue)
return neh.validateValueWithMapPattern(typedKey, value)
case []interface{}:
return neh.validateValueWithSlicePattern(typedKey, nValue)
return neh.validateValueWithSlicePattern(typedKey, value)
default:
glog.Error("Unsupported type %V", typedKey)
return false

View file

@ -17,7 +17,7 @@ type OperatorHandler interface {
}
//VariableSubstitutionHandler defines the handler function for variable substitution
type VariableSubstitutionHandler = func(ctx context.EvalInterface, pattern interface{}) interface{}
type VariableSubstitutionHandler = func(ctx context.EvalInterface, pattern interface{}) (interface{}, error)
//CreateOperatorHandler returns the operator handler based on the operator used in condition
func CreateOperatorHandler(ctx context.EvalInterface, op kyverno.ConditionOperator, subHandler VariableSubstitutionHandler) OperatorHandler {

View file

@ -1,91 +1,82 @@
package variables
import (
"fmt"
"regexp"
"strings"
// // ValidateVariables validates if referenced path is present
// // return empty string if all paths are valid, otherwise return invalid path
// func ValidateVariables(ctx context.EvalInterface, pattern interface{}) string {
// var pathsNotPresent []string
// variableList := extractVariables(pattern)
// for _, variable := range variableList {
// if len(variable) == 2 {
// varName := variable[0]
// varValue := variable[1]
// glog.V(3).Infof("validating variable %s", varName)
// val, err := ctx.Query(varValue)
// if err == nil && val == nil {
// // path is not present, returns nil interface
// pathsNotPresent = append(pathsNotPresent, varValue)
// }
// }
// }
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/context"
)
// if len(pathsNotPresent) != 0 {
// return strings.Join(pathsNotPresent, ";")
// }
// return ""
// }
// ValidateVariables validates if referenced path is present
// return empty string if all paths are valid, otherwise return invalid path
func ValidateVariables(ctx context.EvalInterface, pattern interface{}) string {
var pathsNotPresent []string
variableList := extractVariables(pattern)
for _, variable := range variableList {
if len(variable) == 2 {
varName := variable[0]
varValue := variable[1]
glog.V(3).Infof("validating variable %s", varName)
val, err := ctx.Query(varValue)
if err == nil && val == nil {
// path is not present, returns nil interface
pathsNotPresent = append(pathsNotPresent, varValue)
}
}
}
// // extractVariables extracts variables in the pattern
// func extractVariables(pattern interface{}) [][]string {
// switch typedPattern := pattern.(type) {
// case map[string]interface{}:
// return extractMap(typedPattern)
// case []interface{}:
// return extractArray(typedPattern)
// case string:
// return extractValue(typedPattern)
// default:
// fmt.Printf("variable type %T", typedPattern)
// return nil
// }
// }
if len(pathsNotPresent) != 0 {
return strings.Join(pathsNotPresent, ";")
}
return ""
}
// func extractMap(patternMap map[string]interface{}) [][]string {
// var variableList [][]string
// extractVariables extracts variables in the pattern
func extractVariables(pattern interface{}) [][]string {
switch typedPattern := pattern.(type) {
case map[string]interface{}:
return extractMap(typedPattern)
case []interface{}:
return extractArray(typedPattern)
case string:
return extractValue(typedPattern)
default:
fmt.Printf("variable type %T", typedPattern)
return nil
}
}
// for _, patternElement := range patternMap {
// if vars := extractVariables(patternElement); vars != nil {
// variableList = append(variableList, vars...)
// }
// }
// return variableList
// }
func extractMap(patternMap map[string]interface{}) [][]string {
var variableList [][]string
// func extractArray(patternList []interface{}) [][]string {
// var variableList [][]string
for _, patternElement := range patternMap {
if vars := extractVariables(patternElement); vars != nil {
variableList = append(variableList, vars...)
}
}
return variableList
}
// for _, patternElement := range patternList {
// if vars := extractVariables(patternElement); vars != nil {
// variableList = append(variableList, vars...)
// }
// }
// return variableList
// }
func extractArray(patternList []interface{}) [][]string {
var variableList [][]string
// func extractValue(valuePattern string) [][]string {
// operatorVariable := getOperator(valuePattern)
// variable := valuePattern[len(operatorVariable):]
// return extractValueVariable(variable)
// }
for _, patternElement := range patternList {
if vars := extractVariables(patternElement); vars != nil {
variableList = append(variableList, vars...)
}
}
return variableList
}
func extractValue(valuePattern string) [][]string {
operatorVariable := getOperator(valuePattern)
variable := valuePattern[len(operatorVariable):]
return extractValueVariable(variable)
}
// returns multiple variable match groups
func extractValueVariable(valuePattern string) [][]string {
variableRegex := regexp.MustCompile(variableRegex)
groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
if len(groups) == 0 {
// no variables
return nil
}
// group[*] <- all the matches
// group[*][0] <- group match
// group[*][1] <- group capture value
return groups
}
// // returns multiple variable match groups
// func extractValueVariable(valuePattern string) [][]string {
// variableRegex := regexp.MustCompile(variableRegex)
// groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
// if len(groups) == 0 {
// // no variables
// return nil
// }
// // group[*] <- all the matches
// // group[*][0] <- group match
// // group[*][1] <- group capture value
// return groups
// }

View file

@ -1,175 +0,0 @@
package variables
import (
"encoding/json"
"fmt"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
"gotest.tools/assert"
authenticationv1 "k8s.io/api/authentication/v1"
)
func Test_ExtractVariables(t *testing.T) {
patternRaw := []byte(`
{
"name": "ns-owner-{{request.userInfo.username}}",
"data": {
"rules": [
{
"apiGroups": [
""
],
"resources": [
"namespaces"
],
"verbs": [
"*"
],
"resourceNames": [
"{{request.object.metadata.name}}"
]
}
]
}
}
`)
var pattern interface{}
if err := json.Unmarshal(patternRaw, &pattern); err != nil {
t.Error(err)
}
vars := extractVariables(pattern)
result := [][]string{{"{{request.userInfo.username}}", "request.userInfo.username"},
{"{{request.object.metadata.name}}", "request.object.metadata.name"}}
assert.Assert(t, len(vars) == len(result), fmt.Sprintf("result does not match, var: %s", vars))
}
func Test_ValidateVariables_NoVariable(t *testing.T) {
patternRaw := []byte(`
{
"name": "ns-owner",
"data": {
"rules": [
{
"apiGroups": [
""
],
"resources": [
"namespaces"
],
"verbs": [
"*"
],
"resourceNames": [
"Pod"
]
}
]
}
}
`)
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
// userInfo
userReqInfo := kyverno.RequestInfo{
AdmissionUserInfo: authenticationv1.UserInfo{
Username: "user1",
},
}
var pattern, resource interface{}
assert.NilError(t, json.Unmarshal(patternRaw, &pattern))
assert.NilError(t, json.Unmarshal(resourceRaw, &resource))
var err error
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
if err != nil {
t.Error(err)
}
err = ctx.AddUserInfo(userReqInfo)
if err != nil {
t.Error(err)
}
invalidPaths := ValidateVariables(ctx, pattern)
assert.Assert(t, len(invalidPaths) == 0)
}
func Test_ValidateVariables(t *testing.T) {
patternRaw := []byte(`
{
"name": "ns-owner-{{request.userInfo.username}}",
"data": {
"rules": [
{
"apiGroups": [
""
],
"resources": [
"namespaces"
],
"verbs": [
"*"
],
"resourceNames": [
"{{request.object.metadata.name1}}"
]
}
]
}
}
`)
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
// userInfo
userReqInfo := kyverno.RequestInfo{
AdmissionUserInfo: authenticationv1.UserInfo{
Username: "user1",
},
}
var pattern, resource interface{}
assert.NilError(t, json.Unmarshal(patternRaw, &pattern))
assert.NilError(t, json.Unmarshal(resourceRaw, &resource))
ctx := context.NewContext()
var err error
err = ctx.AddResource(resourceRaw)
if err != nil {
t.Error(err)
}
err = ctx.AddUserInfo(userReqInfo)
if err != nil {
t.Error(err)
}
invalidPaths := ValidateVariables(ctx, pattern)
assert.Assert(t, len(invalidPaths) > 0)
}

View file

@ -1,146 +0,0 @@
package variables
import (
"regexp"
"strings"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/operator"
)
const variableRegex = `\{\{([^{}]*)\}\}`
//SubstituteVariables substitutes the JMESPATH with variable substitution
// supported substitutions
// - no operator + variable(string,object)
// unsupported substitutions
// - operator + variable(object) -> as we dont support operators with object types
func SubstituteVariables(ctx context.EvalInterface, pattern interface{}) interface{} {
// var err error
switch typedPattern := pattern.(type) {
case map[string]interface{}:
return substituteMap(ctx, typedPattern)
case []interface{}:
return substituteArray(ctx, typedPattern)
case string:
// variable substitution
return substituteValue(ctx, typedPattern)
default:
return pattern
}
}
func substituteMap(ctx context.EvalInterface, patternMap map[string]interface{}) map[string]interface{} {
for key, patternElement := range patternMap {
value := SubstituteVariables(ctx, patternElement)
patternMap[key] = value
}
return patternMap
}
func substituteArray(ctx context.EvalInterface, patternList []interface{}) []interface{} {
for idx, patternElement := range patternList {
value := SubstituteVariables(ctx, patternElement)
patternList[idx] = value
}
return patternList
}
func substituteValue(ctx context.EvalInterface, valuePattern string) interface{} {
// patterns supported
// - operator + string
// operator + variable
operatorVariable := getOperator(valuePattern)
variable := valuePattern[len(operatorVariable):]
// substitute variable with value
value := getValueQuery(ctx, variable)
if operatorVariable == "" {
// default or operator.Equal
// equal + string variable
// object variable
return value
}
// operator + string variable
switch value.(type) {
case string:
return string(operatorVariable) + value.(string)
default:
glog.Infof("cannot use operator with object variables. operator used %s in pattern %v", string(operatorVariable), valuePattern)
var emptyInterface interface{}
return emptyInterface
}
}
func getValueQuery(ctx context.EvalInterface, valuePattern string) interface{} {
var emptyInterface interface{}
// extract variable {{<variable>}}
validRegex := regexp.MustCompile(variableRegex)
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
// can have multiple variables in a single value pattern
// var Map <variable,value>
varMap := getValues(ctx, groups)
if len(varMap) == 0 {
// there are no varaiables
// return the original value
return valuePattern
}
// only substitute values if all the variable values are of type string
if isAllVarStrings(varMap) {
newVal := valuePattern
for key, value := range varMap {
if val, ok := value.(string); ok {
newVal = strings.Replace(newVal, key, val, -1)
}
}
return newVal
}
// we do not support multiple substitution per statement for non-string types
for _, value := range varMap {
return value
}
return emptyInterface
}
// returns map of variables as keys and variable values as values
func getValues(ctx context.EvalInterface, groups [][]string) map[string]interface{} {
var emptyInterface interface{}
subs := map[string]interface{}{}
for _, group := range groups {
if len(group) == 2 {
// 0th is string
varName := group[0]
varValue := group[1]
variable, err := ctx.Query(varValue)
if err != nil {
glog.V(4).Infof("variable substitution failed for query %s: %v", varName, err)
subs[varName] = emptyInterface
continue
}
if variable == nil {
subs[varName] = emptyInterface
} else {
subs[varName] = variable
}
}
}
return subs
}
func isAllVarStrings(subVar map[string]interface{}) bool {
for _, value := range subVar {
if _, ok := value.(string); !ok {
return false
}
}
return true
}
func getOperator(pattern string) string {
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
if operatorVariable == operator.Equal {
return ""
}
return string(operatorVariable)
}

View file

@ -1,80 +1,80 @@
package variables
import (
"fmt"
"regexp"
"strconv"
)
// import (
// "fmt"
// "regexp"
// "strconv"
// )
//CheckVariables checks if the variable regex has been used
func CheckVariables(pattern interface{}, variables []string, path string) error {
switch typedPattern := pattern.(type) {
case map[string]interface{}:
return checkMap(typedPattern, variables, path)
case []interface{}:
return checkArray(typedPattern, variables, path)
case string:
return checkValue(typedPattern, variables, path)
default:
return nil
}
}
// //CheckVariables checks if the variable regex has been used
// func CheckVariables(pattern interface{}, variables []string, path string) error {
// switch typedPattern := pattern.(type) {
// case map[string]interface{}:
// return checkMap(typedPattern, variables, path)
// case []interface{}:
// return checkArray(typedPattern, variables, path)
// case string:
// return checkValue(typedPattern, variables, path)
// default:
// return nil
// }
// }
func checkMap(patternMap map[string]interface{}, variables []string, path string) error {
for patternKey, patternElement := range patternMap {
// func checkMap(patternMap map[string]interface{}, variables []string, path string) error {
// for patternKey, patternElement := range patternMap {
if err := CheckVariables(patternElement, variables, path+patternKey+"/"); err != nil {
return err
}
}
return nil
}
// if err := CheckVariables(patternElement, variables, path+patternKey+"/"); err != nil {
// return err
// }
// }
// return nil
// }
func checkArray(patternList []interface{}, variables []string, path string) error {
for idx, patternElement := range patternList {
if err := CheckVariables(patternElement, variables, path+strconv.Itoa(idx)+"/"); err != nil {
return err
}
}
return nil
}
// func checkArray(patternList []interface{}, variables []string, path string) error {
// for idx, patternElement := range patternList {
// if err := CheckVariables(patternElement, variables, path+strconv.Itoa(idx)+"/"); err != nil {
// return err
// }
// }
// return nil
// }
func checkValue(valuePattern string, variables []string, path string) error {
operatorVariable := getOperator(valuePattern)
variable := valuePattern[len(operatorVariable):]
if checkValueVariable(variable, variables) {
return fmt.Errorf(path + valuePattern)
}
return nil
}
// func checkValue(valuePattern string, variables []string, path string) error {
// operatorVariable := getOperator(valuePattern)
// variable := valuePattern[len(operatorVariable):]
// if checkValueVariable(variable, variables) {
// return fmt.Errorf(path + valuePattern)
// }
// return nil
// }
func checkValueVariable(valuePattern string, variables []string) bool {
variableRegex := regexp.MustCompile(variableRegex)
groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
if len(groups) == 0 {
// no variables
return false
}
// if variables are defined, check against the list of variables to be filtered
for _, group := range groups {
if len(group) == 2 {
// group[0] -> {{variable}}
// group[1] -> variable
if variablePatternSearch(group[1], variables) {
return true
}
}
}
return false
}
// func checkValueVariable(valuePattern string, variables []string) bool {
// variableRegex := regexp.MustCompile(variableRegex)
// groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
// if len(groups) == 0 {
// // no variables
// return false
// }
// // if variables are defined, check against the list of variables to be filtered
// for _, group := range groups {
// if len(group) == 2 {
// // group[0] -> {{variable}}
// // group[1] -> variable
// if variablePatternSearch(group[1], variables) {
// return true
// }
// }
// }
// return false
// }
func variablePatternSearch(pattern string, regexs []string) bool {
for _, regex := range regexs {
varRegex := regexp.MustCompile(regex)
found := varRegex.FindString(pattern)
if found != "" {
return true
}
}
return true
}
// func variablePatternSearch(pattern string, regexs []string) bool {
// for _, regex := range regexs {
// varRegex := regexp.MustCompile(regex)
// found := varRegex.FindString(pattern)
// if found != "" {
// return true
// }
// }
// return true
// }

View file

@ -58,12 +58,16 @@ func Test_variablesub1(t *testing.T) {
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-user1"}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
t.Error(err)
@ -80,12 +84,15 @@ func Test_variablesub1(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(resultMap, resultRaw) {
if !reflect.DeepEqual(resultRaw, resultMap) {
t.Log(string(resultMap))
t.Log(string(resultRaw))
t.Error("result does not match")
@ -139,12 +146,16 @@ func Test_variablesub_multiple(t *testing.T) {
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-n1-user1-bindings"}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -163,11 +174,14 @@ func Test_variablesub_multiple(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Log(string(resultMap))
t.Log(string(resultRaw))
@ -219,12 +233,16 @@ func Test_variablesubstitution(t *testing.T) {
Username: "user1",
},
}
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -243,8 +261,10 @@ func Test_variablesubstitution(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
@ -279,12 +299,16 @@ func Test_variableSubstitutionValue(t *testing.T) {
resultMap := []byte(`{"spec":{"name":"temp"}}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -298,8 +322,10 @@ func Test_variableSubstitutionValue(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
@ -331,12 +357,16 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
`)
resultMap := []byte(`{"spec":{"name":"!temp"}}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -350,14 +380,16 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
t.Log(string(resultRaw))
t.Log(string(resultMap))
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Log(string(resultRaw))
t.Log(string(resultMap))
t.Error("result does not match")
}
}
@ -383,14 +415,17 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
}
}
`)
resultMap := []byte(`{"spec":{"name":null}}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -404,16 +439,11 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if err != nil {
t.Error(err)
}
t.Log(string(resultRaw))
t.Log(string(resultMap))
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Error("result does not match")
if _, err := SubstituteVars(ctx, patternCopy); err == nil {
t.Log("expected to fails")
t.Fail()
}
}
func Test_variableSubstitutionObject(t *testing.T) {
@ -444,12 +474,16 @@ func Test_variableSubstitutionObject(t *testing.T) {
`)
resultMap := []byte(`{"spec":{"variable":{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}}}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -463,14 +497,16 @@ func Test_variableSubstitutionObject(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
t.Log(string(resultRaw))
t.Log(string(resultMap))
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Log(string(resultRaw))
t.Log(string(resultMap))
t.Error("result does not match")
}
}
@ -504,12 +540,16 @@ func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
resultMap := []byte(`{"spec":{"variable":null}}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -523,14 +563,16 @@ func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
t.Log(string(resultRaw))
t.Log(string(resultMap))
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Log(string(resultRaw))
t.Log(string(resultMap))
t.Error("result does not match")
}
}
@ -565,12 +607,16 @@ func Test_variableSubstitutionMultipleObject(t *testing.T) {
resultMap := []byte(`{"spec":{"var":"temp1","variable":{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}}}`)
var pattern, resource interface{}
var pattern, patternCopy, resource interface{}
var err error
err = json.Unmarshal(patternMap, &pattern)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(patternMap, &patternCopy)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(resourceRaw, &resource)
if err != nil {
@ -584,14 +630,16 @@ func Test_variableSubstitutionMultipleObject(t *testing.T) {
t.Error(err)
}
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
t.Error(err)
}
resultRaw, err := json.Marshal(patternCopy)
if err != nil {
t.Error(err)
}
t.Log(string(resultRaw))
t.Log(string(resultMap))
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Log(string(resultRaw))
t.Log(string(resultMap))
t.Error("result does not match")
}
}

View file

@ -11,16 +11,19 @@ import (
"github.com/nirmata/kyverno/pkg/engine/operator"
)
const variableRegex = `\{\{([^{}]*)\}\}`
//SubstituteVars replaces the variables with the values defined in the context
// - if any variable is invaid or has nil value, it is considered as a failed varable substitution
func SubstituteVars(ctx context.EvalInterface, pattern interface{}) error {
func SubstituteVars(ctx context.EvalInterface, pattern interface{}) (interface{}, error) {
println(&pattern)
errs := []error{}
subVars(ctx, pattern, "", &errs)
pattern = subVars(ctx, pattern, "", &errs)
if len(errs) == 0 {
// no error while parsing the pattern
return nil
return pattern, nil
}
return fmt.Errorf("variable(s) not found or has nil values: %v", errs)
return pattern, fmt.Errorf("variable(s) not found or has nil values: %v", errs)
}
func subVars(ctx context.EvalInterface, pattern interface{}, path string, errs *[]error) interface{} {
@ -55,9 +58,16 @@ func subArray(ctx context.EvalInterface, patternList []interface{}, path string,
return patternList
}
func subVal(ctx context.EvalInterface, valuePattern string, path string, errs *[]error) interface{} {
operatorVariable := getOp(valuePattern)
variable := valuePattern[len(operatorVariable):]
func subVal(ctx context.EvalInterface, valuePattern interface{}, path string, errs *[]error) interface{} {
var emptyInterface interface{}
valueStr, ok := valuePattern.(string)
if !ok {
glog.Infof("failed to convert %v to string", valuePattern)
return emptyInterface
}
operatorVariable := getOp(valueStr)
variable := valueStr[len(operatorVariable):]
// substitute variable with value
value, failedVars := getValQuery(ctx, variable)
// if there are failedVars at this level
@ -70,15 +80,17 @@ func subVal(ctx context.EvalInterface, valuePattern string, path string, errs *[
// default or operator.Equal
// equal + string value
// object variable
valuePattern = value
return value
}
// operator + string variable
switch value.(type) {
case string:
valuePattern = string(operatorVariable) + value.(string)
return string(operatorVariable) + value.(string)
default:
glog.Infof("cannot use operator with object variables. operator used %s in pattern %v", string(operatorVariable), valuePattern)
var emptyInterface interface{}
valuePattern = emptyInterface
return emptyInterface
}

View file

@ -63,7 +63,7 @@ func Test_subVars_success(t *testing.T) {
t.Error(err)
}
if err := SubstituteVars(ctx, pattern); err != nil {
if _, err := SubstituteVars(ctx, pattern); err != nil {
t.Error(err)
}
}
@ -124,7 +124,7 @@ func Test_subVars_failed(t *testing.T) {
t.Error(err)
}
if err := SubstituteVars(ctx, pattern); err == nil {
if _, err := SubstituteVars(ctx, pattern); err == nil {
t.Error("error is expected")
}
}

View file

@ -126,22 +126,19 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
var err error
var mode ResourceMode
var noGenResource kyverno.ResourceSpec
// convert to unstructured Resource
genUnst, err := getUnstrRule(rule.Generation.DeepCopy())
if err != nil {
return noGenResource, err
}
glog.V(4).Info("applyRule1 %v", genUnst)
// Variable substitutions
// format : {{<variable_name}}
// - if there is variables that are not defined the context -> results in error and rule is not applied
// - valid variables are replaced with the values
if err := variables.SubstituteVars(ctx, genUnst.Object); err != nil {
if _, err := variables.SubstituteVars(ctx, genUnst.Object); err != nil {
return noGenResource, err
}
glog.V(4).Info("applyRule2 %v", genUnst.Object)
genKind, _, err := unstructured.NestedString(genUnst.Object, "kind")
if err != nil {
return noGenResource, err
@ -175,6 +172,10 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
} else {
rdata, mode, err = manageClone(genKind, genNamespace, genName, genCopy, client, resource)
}
if err != nil {
return noGenResource, err
}
if rdata == nil {
// existing resource contains the configuration
return newGenResource, nil
@ -265,7 +266,6 @@ func manageClone(kind, namespace, name string, clone map[string]interface{}, cli
return nil, Skip, err
}
newRName, _, err := unstructured.NestedString(clone, "name")
if err != nil {
return nil, Skip, err
}
@ -275,10 +275,11 @@ func manageClone(kind, namespace, name string, clone map[string]interface{}, cli
return nil, Skip, nil
}
glog.V(4).Infof("check if resource %s/%s/%s exists", kind, newRNs, newRName)
// check if the resource as reference in clone exists?
obj, err := client.GetResource(kind, newRNs, newRName)
if err != nil {
return nil, Skip, err
return nil, Skip, fmt.Errorf("reference clone resource %s/%s/%s not found. %v", kind, newRNs, newRName, err)
}
// create the resource based on the reference clone
return obj.UnstructuredContent(), Create, nil
@ -300,7 +301,7 @@ const (
func checkResource(newResourceSpec interface{}, resource *unstructured.Unstructured) error {
// check if the resource spec if a subset of the resource
if path, err := validate.ValidateResourceWithPattern1(resource.Object, newResourceSpec); err != nil {
glog.V(4).Info("Failed to match the resource at path %s: err", path, err)
glog.V(4).Infof("Failed to match the resource at path %s: err %v", path, err)
return err
}
return nil