mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-29 10:55:05 +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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/jmespath/go-jmespath"
|
||||
jmespath "github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
//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)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("incorrect query %s: %v", query, err)
|
||||
return emptyResult, err
|
||||
return emptyResult, fmt.Errorf("incorrect query %s: %v", query, err)
|
||||
}
|
||||
// search
|
||||
ctx.mu.RLock()
|
||||
|
@ -22,14 +23,14 @@ func (ctx *Context) Query(query string) (interface{}, error) {
|
|||
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(ctx.jsonRaw, &data); err != nil {
|
||||
glog.V(4).Infof("failed to unmarshall context")
|
||||
return emptyResult, err
|
||||
glog.V(4).Infof("failed to unmarshall context: %v", err)
|
||||
return emptyResult, fmt.Errorf("failed to unmarshall context: %v", err)
|
||||
}
|
||||
|
||||
result, err := queryPath.Search(data)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
|
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"
|
||||
)
|
||||
|
||||
const variableRegex = `\{\{([^{}]*)\}\}`
|
||||
|
||||
//SubstituteVariables substitutes the JMESPATH with variable substitution
|
||||
// supported substitutions
|
||||
// - 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{} {
|
||||
var emptyInterface interface{}
|
||||
// extract variable {{<variable>}}
|
||||
validRegex := regexp.MustCompile(`\{\{([^{}]*)\}\}`)
|
||||
validRegex := regexp.MustCompile(variableRegex)
|
||||
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
|
||||
// can have multiple variables in a single value pattern
|
||||
// var Map <variable,value>
|
||||
|
|
|
@ -49,7 +49,7 @@ func checkValue(valuePattern string, variables []string, path string) error {
|
|||
}
|
||||
|
||||
func checkValueVariable(valuePattern string, variables []string) bool {
|
||||
variableRegex := regexp.MustCompile("^{{(.*)}}$")
|
||||
variableRegex := regexp.MustCompile(variableRegex)
|
||||
groups := variableRegex.FindStringSubmatch(valuePattern)
|
||||
if len(groups) < 2 {
|
||||
return false
|
||||
|
|
Loading…
Add table
Reference in a new issue