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:
parent
d855247a98
commit
5cee543755
26 changed files with 456 additions and 998 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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(©Condition)
|
||||
copy = append(copy, copyCondition)
|
||||
copy = append(copy, *condition.DeepCopy())
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
@ -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*']")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
// }
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
// }
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue