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

Merge pull request #43 from nirmata/lists-validation

Lists validation
This commit is contained in:
shuting 2019-05-16 18:14:38 -07:00 committed by GitHub
commit e499188948
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 444 additions and 103 deletions

View file

@ -1,11 +1,9 @@
package engine
import (
"fmt"
"log"
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kube-policy/pkg/engine/mutation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -37,7 +35,7 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
continue
}
ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
if err != nil {
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
continue
@ -62,17 +60,12 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
// TODO: plan to support all kinds of generator
func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation) ([]GenerationResponse, error) {
var generateResps []GenerationResponse
var generationResponse []GenerationResponse
if generator == nil {
return nil, nil
}
err := generator.Validate()
if err != nil {
return nil, fmt.Errorf("Generator for '%s' is invalid: %s", generator.Kind, err)
}
namespaceName := mutation.ParseNameFromObject(rawResource)
generateResps = append(generateResps, GenerationResponse{generator, namespaceName})
return generateResps, nil
namespaceName := ParseNameFromObject(rawResource)
generationResponse = append(generationResponse, GenerationResponse{generator, namespaceName})
return generationResponse, nil
}

View file

@ -26,7 +26,7 @@ func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersio
continue
}
ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
if err != nil {
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
continue

View file

@ -1,26 +0,0 @@
package mutation
import (
"testing"
)
func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) {
if len(expected) != len(actual) {
t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual)
return
}
for idx, val := range actual {
if val != expected[idx] {
t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual)
}
}
}
func assertEqData(t *testing.T, expected, actual []byte) {
assertEqDataImpl(t, expected, actual, "%x")
}
func assertEqStringAndData(t *testing.T, str string, data []byte) {
assertEqDataImpl(t, []byte(str), data, "%s")
}

View file

@ -1,4 +1,4 @@
package mutation
package engine
import (
"encoding/json"
@ -6,10 +6,46 @@ import (
"github.com/minio/minio/pkg/wildcard"
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
// ResourceMeetsRules checks requests kind, name and labels to fit the policy
func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescription, gvk metav1.GroupVersionKind) (bool, error) {
if description.Kind != gvk.Kind {
return false, nil
}
if resourceRaw != nil {
meta := ParseMetadataFromObject(resourceRaw)
name := ParseNameFromObject(resourceRaw)
if description.Name != nil {
if !wildcard.Match(*description.Name, name) {
return false, nil
}
}
if description.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(description.Selector)
if err != nil {
return false, err
}
labelMap := ParseLabelsFromMetadata(meta)
if !selector.Matches(labelMap) {
return false, nil
}
}
}
return true, nil
}
func ParseMetadataFromObject(bytes []byte) map[string]interface{} {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
@ -68,38 +104,3 @@ func ParseRegexPolicyResourceName(policyResourceName string) (string, bool) {
}
return strings.Trim(regex[1], " "), true
}
// ResourceMeetsRules checks requests kind, name and labels to fit the policy
func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescription, gvk metav1.GroupVersionKind) (bool, error) {
if description.Kind != gvk.Kind {
return false, nil
}
if resourceRaw != nil {
meta := ParseMetadataFromObject(resourceRaw)
name := ParseNameFromObject(resourceRaw)
if description.Name != nil {
if !wildcard.Match(*description.Name, name) {
return false, nil
}
}
if description.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(description.Selector)
if err != nil {
return false, err
}
labelMap := ParseLabelsFromMetadata(meta)
if !selector.Matches(labelMap) {
return false, nil
}
}
}
return true, nil
}

View file

@ -5,11 +5,14 @@ import (
"fmt"
"log"
"github.com/minio/minio/pkg/wildcard"
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kube-policy/pkg/engine/mutation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Validate handles validating admission request
// Checks the target resourse for rules defined in the policy
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) bool {
var resource interface{}
json.Unmarshal(rawResource, &resource)
@ -28,7 +31,7 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
continue
}
ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
ok, err := ResourceMeetsRules(rawResource, rule.ResourceDescription, gvk)
if err != nil {
log.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
continue
@ -43,60 +46,196 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
continue
}
if err := traverseAndValidate(resource, rule.Validation.Pattern); err != nil {
log.Printf("Validation with the rule %s has failed %s: %s\n", rule.Name, err.Error(), *rule.Validation.Message)
if !validateMap(resource, rule.Validation.Pattern) {
log.Printf("Validation with the rule %s has failed: %s\n", rule.Name, *rule.Validation.Message)
allowed = false
} else {
log.Printf("Validation rule %s is successful %s: %s\n", rule.Name, err.Error(), *rule.Validation.Message)
log.Printf("Validation rule %s is successful\n", rule.Name)
}
}
return allowed
}
func traverseAndValidate(resourcePart, patternPart interface{}) error {
func validateMap(resourcePart, patternPart interface{}) bool {
pattern := patternPart.(map[string]interface{})
resource, ok := resourcePart.(map[string]interface{})
if !ok {
fmt.Printf("Validating error: expected Map, found %T\n", resourcePart)
return false
}
for key, value := range pattern {
if wrappedWithParentheses(key) {
key = key[1 : len(key)-1]
}
if !validateMapElement(resource[key], value) {
return false
}
}
return true
}
func validateArray(resourcePart, patternPart interface{}) bool {
patternArray := patternPart.([]interface{})
resourceArray, ok := resourcePart.([]interface{})
if !ok {
fmt.Printf("Validating error: expected array, found %T\n", resourcePart)
return false
}
switch pattern := patternArray[0].(type) {
case map[string]interface{}:
anchors, err := getAnchorsFromMap(pattern)
if err != nil {
fmt.Printf("Validating error: %v\n", err)
return false
}
for _, value := range resourceArray {
resource, ok := value.(map[string]interface{})
if !ok {
fmt.Printf("Validating error: expected Map, found %T\n", resourcePart)
return false
}
if skipArrayObject(resource, anchors) {
continue
}
if !validateMap(resource, pattern) {
return false
}
}
return true
default:
for _, value := range resourceArray {
if !checkSingleValue(value, patternArray[0]) {
return false
}
}
}
return true
}
func validateMapElement(resourcePart, patternPart interface{}) bool {
switch pattern := patternPart.(type) {
case map[string]interface{}:
dictionary, ok := resourcePart.(map[string]interface{})
if !ok {
return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart)
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
return false
}
var err error
for key, value := range pattern {
err = traverseAndValidate(dictionary[key], value)
}
return err
return validateMap(dictionary, pattern)
case []interface{}:
array, ok := resourcePart.([]interface{})
if !ok {
return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart)
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
return false
}
var err error
for i, value := range pattern {
err = traverseAndValidate(array[i], value)
}
return err
return validateArray(array, pattern)
case string:
str := resourcePart.(string)
if !checkForWildcard(str, pattern) {
return fmt.Errorf("Value %s has not passed wildcard check %s", str, pattern)
}
default:
return fmt.Errorf("Received unknown type: %T", patternPart)
str, ok := resourcePart.(string)
if !ok {
fmt.Printf("Validating error: expected %T, found %T\n", patternPart, resourcePart)
return false
}
return nil
return checkSingleValue(str, pattern)
default:
fmt.Printf("Validating error: unknown type in map: %T\n", patternPart)
return false
}
}
func getAnchorsFromMap(pattern map[string]interface{}) (map[string]interface{}, error) {
result := make(map[string]interface{})
for key, value := range pattern {
if wrappedWithParentheses(key) {
result[key] = value
}
}
return result, nil
}
func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
if !checkSingleValue(value, pattern) {
return true
}
}
return false
}
func checkSingleValue(value, pattern interface{}) bool {
switch typedPattern := pattern.(type) {
case string:
switch typedValue := value.(type) {
case string:
return checkForWildcard(typedValue, typedPattern)
case float64:
return checkForOperator(typedValue, typedPattern)
case int:
return checkForOperator(float64(typedValue), typedPattern)
default:
fmt.Printf("Validating error: expected string or numerical type, found %T, pattern: %s\n", value, typedPattern)
return false
}
case float64:
num, ok := value.(float64)
if !ok {
fmt.Printf("Validating error: expected float, found %T\n", value)
return false
}
return typedPattern == num
case int:
num, ok := value.(int)
if !ok {
fmt.Printf("Validating error: expected int, found %T\n", value)
return false
}
return typedPattern == num
default:
fmt.Printf("Validating error: expected pattern (string or numerical type), found %T\n", pattern)
return false
}
}
func checkForWildcard(value, pattern string) bool {
return value == pattern
return wildcard.Match(pattern, value)
}
func checkForOperator(value int, pattern string) bool {
func checkForOperator(value float64, pattern string) bool {
return true
}
func wrappedWithParentheses(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
}

View file

@ -0,0 +1,234 @@
package engine
import (
"encoding/json"
"testing"
"gotest.tools/assert"
)
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
str := "(something)"
assert.Assert(t, wrappedWithParentheses(str))
}
func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) {
str := "()"
assert.Assert(t, wrappedWithParentheses(str))
}
func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) {
str := "something"
assert.Assert(t, !wrappedWithParentheses(str))
}
func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) {
str := "(something"
assert.Assert(t, !wrappedWithParentheses(str))
}
func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) {
str := "something)"
assert.Assert(t, !wrappedWithParentheses(str))
}
func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) {
str := "so)m(et(hin)g"
assert.Assert(t, !wrappedWithParentheses(str))
}
func TestWrappedWithParentheses_Empty(t *testing.T) {
str := ""
assert.Assert(t, !wrappedWithParentheses(str))
}
func TestCheckForWildcard_AsteriskTest(t *testing.T) {
pattern := "*"
value := "anything"
empty := ""
assert.Assert(t, checkForWildcard(value, pattern))
assert.Assert(t, checkForWildcard(empty, pattern))
}
func TestCheckForWildcard_LeftAsteriskTest(t *testing.T) {
pattern := "*right"
value := "leftright"
right := "right"
assert.Assert(t, checkForWildcard(value, pattern))
assert.Assert(t, checkForWildcard(right, pattern))
value = "leftmiddle"
middle := "middle"
assert.Assert(t, !checkForWildcard(value, pattern))
assert.Assert(t, !checkForWildcard(middle, pattern))
}
func TestCheckForWildcard_MiddleAsteriskTest(t *testing.T) {
pattern := "ab*ba"
value := "abbba"
assert.Assert(t, checkForWildcard(value, pattern))
value = "abbca"
assert.Assert(t, !checkForWildcard(value, pattern))
}
func TestCheckForWildcard_QuestionMark(t *testing.T) {
pattern := "ab?ba"
value := "abbba"
assert.Assert(t, checkForWildcard(value, pattern))
value = "abbbba"
assert.Assert(t, !checkForWildcard(value, pattern))
}
func TestCheckSingleValue_CheckInt(t *testing.T) {
pattern := 89
value := 89
assert.Assert(t, checkSingleValue(value, pattern))
value = 202
assert.Assert(t, !checkSingleValue(value, pattern))
}
func TestCheckSingleValue_CheckFloat(t *testing.T) {
pattern := 89.9091
value := 89.9091
assert.Assert(t, checkSingleValue(value, pattern))
value = 89.9092
assert.Assert(t, !checkSingleValue(value, pattern))
}
func TestCheckSingleValue_CheckOperatorMore(t *testing.T) {
pattern := ">10"
value := 89
assert.Assert(t, checkSingleValue(value, pattern))
pattern = ">10"
floatValue := 89.901
assert.Assert(t, checkSingleValue(floatValue, pattern))
}
func TestCheckSingleValue_CheckWildcard(t *testing.T) {
pattern := "nirmata_*"
value := "nirmata_awesome"
assert.Assert(t, checkSingleValue(value, pattern))
pattern = "nirmata_*"
value = "spasex_awesome"
assert.Assert(t, !checkSingleValue(value, pattern))
pattern = "g?t"
value = "git"
assert.Assert(t, checkSingleValue(value, pattern))
}
func TestSkipArrayObject_OneAnchor(t *testing.T) {
rawAnchors := []byte(`{"(name)": "nirmata-*"}`)
rawResource := []byte(`{"name": "nirmata-resource", "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`)
var resource, anchor map[string]interface{}
json.Unmarshal(rawAnchors, &anchor)
json.Unmarshal(rawResource, &resource)
assert.Assert(t, !skipArrayObject(resource, anchor))
}
func TestSkipArrayObject_OneNumberAnchorPass(t *testing.T) {
rawAnchors := []byte(`{"(count)": 1}`)
rawResource := []byte(`{"name": "nirmata-resource", "count": 1, "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`)
var resource, anchor map[string]interface{}
json.Unmarshal(rawAnchors, &anchor)
json.Unmarshal(rawResource, &resource)
assert.Assert(t, !skipArrayObject(resource, anchor))
}
func TestSkipArrayObject_TwoAnchorsPass(t *testing.T) {
rawAnchors := []byte(`{"(name)": "nirmata-*", "(namespace)": "kube-?olicy"}`)
rawResource := []byte(`{"name": "nirmata-resource", "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`)
var resource, anchor map[string]interface{}
json.Unmarshal(rawAnchors, &anchor)
json.Unmarshal(rawResource, &resource)
assert.Assert(t, !skipArrayObject(resource, anchor))
}
func TestSkipArrayObject_TwoAnchorsSkip(t *testing.T) {
rawAnchors := []byte(`{"(name)": "nirmata-*", "(namespace)": "some-?olicy"}`)
rawResource := []byte(`{"name": "nirmata-resource", "namespace": "kube-policy", "object": { "label": "app", "array": [ 1, 2, 3 ]}}`)
var resource, anchor map[string]interface{}
json.Unmarshal(rawAnchors, &anchor)
json.Unmarshal(rawResource, &resource)
assert.Assert(t, skipArrayObject(resource, anchor))
}
func TestGetAnchorsFromMap_ThereAreAnchors(t *testing.T) {
rawMap := []byte(`{"(name)": "nirmata-*", "notAnchor1": 123, "(namespace)": "kube-?olicy", "notAnchor2": "sample-text", "object": { "key1": "value1", "(key2)": "value2"}}`)
var unmarshalled map[string]interface{}
json.Unmarshal(rawMap, &unmarshalled)
actualMap, err := getAnchorsFromMap(unmarshalled)
assert.NilError(t, err)
assert.Equal(t, len(actualMap), 2)
assert.Equal(t, actualMap["(name)"].(string), "nirmata-*")
assert.Equal(t, actualMap["(namespace)"].(string), "kube-?olicy")
}
func TestGetAnchorsFromMap_ThereAreNoAnchors(t *testing.T) {
rawMap := []byte(`{"name": "nirmata-*", "notAnchor1": 123, "namespace": "kube-?olicy", "notAnchor2": "sample-text", "object": { "key1": "value1", "(key2)": "value2"}}`)
var unmarshalled map[string]interface{}
json.Unmarshal(rawMap, &unmarshalled)
actualMap, err := getAnchorsFromMap(unmarshalled)
assert.NilError(t, err)
assert.Assert(t, len(actualMap) == 0)
}
func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) {
rawPattern := []byte(`[ { "(name)": "nirmata-*", "object": [ { "(key1)": "value*", "key2": "value*" } ] } ]`)
rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value1", "key2": "value2" } ] }, { "name": "nirmata-1", "object": [ { "key1": "not_value", "key2": "not_value" } ] } ]`)
var pattern, resource interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.Assert(t, validateMapElement(resource, pattern))
}
func TestValidateMapElement_OneElementInArrayPass(t *testing.T) {
rawPattern := []byte(`[ { "(name)": "nirmata-*", "object": [ { "(key1)": "value*", "key2": "value*" } ] } ]`)
rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value1", "key2": "value2" } ] } ]`)
var pattern, resource interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.Assert(t, validateMapElement(resource, pattern))
}
func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) {
rawPattern := []byte(`[{"(name)": "nirmata-*", "object":[{"(key1)": "value*", "key2": "value*"}]}]`)
rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value5", "key2": "1value1" } ] } ]`)
var pattern, resource interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.Assert(t, !validateMapElement(resource, pattern))
}

View file

@ -147,8 +147,8 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
allPatches = append(allPatches, policyPatches...)
if len(policyPatches) > 0 {
namespace := mutation.ParseNamespaceFromObject(request.Object.Raw)
name := mutation.ParseNameFromObject(request.Object.Raw)
namespace := engine.ParseNamespaceFromObject(request.Object.Raw)
name := engine.ParseNameFromObject(request.Object.Raw)
ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
}
}