mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
validate paths in variable substitution is present
This commit is contained in:
parent
d0a1acbac4
commit
731fdb3e07
5 changed files with 250 additions and 7 deletions
|
@ -2,9 +2,10 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/jmespath/go-jmespath"
|
jmespath "github.com/jmespath/go-jmespath"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Query the JSON context with JMESPATH search path
|
//Query the JSON context with JMESPATH search path
|
||||||
|
@ -14,7 +15,7 @@ func (ctx *Context) Query(query string) (interface{}, error) {
|
||||||
queryPath, err := jmespath.Compile(query)
|
queryPath, err := jmespath.Compile(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(4).Infof("incorrect query %s: %v", query, err)
|
glog.V(4).Infof("incorrect query %s: %v", query, err)
|
||||||
return emptyResult, err
|
return emptyResult, fmt.Errorf("incorrect query %s: %v", query, err)
|
||||||
}
|
}
|
||||||
// search
|
// search
|
||||||
ctx.mu.RLock()
|
ctx.mu.RLock()
|
||||||
|
@ -22,14 +23,14 @@ func (ctx *Context) Query(query string) (interface{}, error) {
|
||||||
|
|
||||||
var data interface{}
|
var data interface{}
|
||||||
if err := json.Unmarshal(ctx.jsonRaw, &data); err != nil {
|
if err := json.Unmarshal(ctx.jsonRaw, &data); err != nil {
|
||||||
glog.V(4).Infof("failed to unmarshall context")
|
glog.V(4).Infof("failed to unmarshall context: %v", err)
|
||||||
return emptyResult, err
|
return emptyResult, fmt.Errorf("failed to unmarshall context: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := queryPath.Search(data)
|
result, err := queryPath.Search(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(4).Infof("failed to search query %s: %v", query, err)
|
glog.V(4).Infof("failed to search query %s: %v", query, err)
|
||||||
return emptyResult, err
|
return emptyResult, fmt.Errorf("failed to search query %s: %v", query, err)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
80
pkg/engine/variables/validatevariables.go
Normal file
80
pkg/engine/variables/validatevariables.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package variables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateVariables validates if referenced path is present
|
||||||
|
func ValidateVariables(ctx context.EvalInterface, pattern interface{}) error {
|
||||||
|
var pathsNotPresent []string
|
||||||
|
variableList := extractVariables(pattern)
|
||||||
|
for i := 0; i < len(variableList)-1; i = i + 2 {
|
||||||
|
p := variableList[i+1]
|
||||||
|
val, err := ctx.Query(p)
|
||||||
|
// reference path is not present
|
||||||
|
if err == nil && val == nil {
|
||||||
|
pathsNotPresent = append(pathsNotPresent, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(variableList) != 0 && len(pathsNotPresent) != 0 {
|
||||||
|
return fmt.Errorf("referenced paths are not present: %s", strings.Join(pathsNotPresent, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractMap(patternMap map[string]interface{}) []string {
|
||||||
|
var variableList []string
|
||||||
|
|
||||||
|
for _, patternElement := range patternMap {
|
||||||
|
if vars := extractVariables(patternElement); vars != nil {
|
||||||
|
variableList = append(variableList, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variableList
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractArray(patternList []interface{}) []string {
|
||||||
|
var variableList []string
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractValueVariable(valuePattern string) []string {
|
||||||
|
variableRegex := regexp.MustCompile(variableRegex)
|
||||||
|
groups := variableRegex.FindStringSubmatch(valuePattern)
|
||||||
|
if len(groups)%2 != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
160
pkg/engine/variables/validatevariables_test.go
Normal file
160
pkg/engine/variables/validatevariables_test.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package variables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"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{}
|
||||||
|
json.Unmarshal(patternRaw, &pattern)
|
||||||
|
|
||||||
|
vars := extractVariables(pattern)
|
||||||
|
result := []string{"{{request.userInfo.username}}", "request.userInfo.username", "{{request.object.metadata.name}}", "request.object.metadata.name"}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(vars, result) {
|
||||||
|
t.Errorf("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))
|
||||||
|
|
||||||
|
ctx := context.NewContext()
|
||||||
|
ctx.AddResource(resourceRaw)
|
||||||
|
ctx.AddUserInfo(userReqInfo)
|
||||||
|
|
||||||
|
err := ValidateVariables(ctx, pattern)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
ctx.AddResource(resourceRaw)
|
||||||
|
ctx.AddUserInfo(userReqInfo)
|
||||||
|
|
||||||
|
err := ValidateVariables(ctx, pattern)
|
||||||
|
assert.Assert(t, err != nil)
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const variableRegex = `\{\{([^{}]*)\}\}`
|
||||||
|
|
||||||
//SubstituteVariables substitutes the JMESPATH with variable substitution
|
//SubstituteVariables substitutes the JMESPATH with variable substitution
|
||||||
// supported substitutions
|
// supported substitutions
|
||||||
// - no operator + variable(string,object)
|
// - no operator + variable(string,object)
|
||||||
|
@ -73,7 +75,7 @@ func substituteValue(ctx context.EvalInterface, valuePattern string) interface{}
|
||||||
func getValueQuery(ctx context.EvalInterface, valuePattern string) interface{} {
|
func getValueQuery(ctx context.EvalInterface, valuePattern string) interface{} {
|
||||||
var emptyInterface interface{}
|
var emptyInterface interface{}
|
||||||
// extract variable {{<variable>}}
|
// extract variable {{<variable>}}
|
||||||
validRegex := regexp.MustCompile(`\{\{([^{}]*)\}\}`)
|
validRegex := regexp.MustCompile(variableRegex)
|
||||||
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
|
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
|
||||||
// can have multiple variables in a single value pattern
|
// can have multiple variables in a single value pattern
|
||||||
// var Map <variable,value>
|
// var Map <variable,value>
|
||||||
|
|
|
@ -49,7 +49,7 @@ func checkValue(valuePattern string, variables []string, path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkValueVariable(valuePattern string, variables []string) bool {
|
func checkValueVariable(valuePattern string, variables []string) bool {
|
||||||
variableRegex := regexp.MustCompile("^{{(.*)}}$")
|
variableRegex := regexp.MustCompile(variableRegex)
|
||||||
groups := variableRegex.FindStringSubmatch(valuePattern)
|
groups := variableRegex.FindStringSubmatch(valuePattern)
|
||||||
if len(groups) < 2 {
|
if len(groups) < 2 {
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Add table
Reference in a new issue