diff --git a/documentation/writing-policies-mutate.md b/documentation/writing-policies-mutate.md
index c76866c97f..8f724fab2a 100644
--- a/documentation/writing-policies-mutate.md
+++ b/documentation/writing-policies-mutate.md
@@ -6,6 +6,12 @@ The ```mutate``` rule contains actions that will be applied to matching resource
Resource mutation occurs before validation, so the validation rules should not contradict the changes performed by the mutation section.
+## Anchors
+| Anchor | Tag | Behavior |
+|------------- |----- |--------------------------------------------- |
+| Conditional | () | Add the specified tag |
+| Add | +() | Add if the tag if not specified |
+
## Patches
This patch adds an init container to all deployments.
diff --git a/documentation/writing-policies-validate.md b/documentation/writing-policies-validate.md
index 19821cc8aa..69ca227142 100644
--- a/documentation/writing-policies-validate.md
+++ b/documentation/writing-policies-validate.md
@@ -33,6 +33,12 @@ A validation rule is expressed as an overlay pattern that expresses the desired
There is no operator for `equals` as providing a field value in the pattern requires equality to the value.
+## Anchors
+| Anchor | Tag | Behavior |
+|------------- |----- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Conditional | () | If tag with the given value is specified, then following resource elements must satisfy the conditions.
e.g.
(image):"*:latest"
imagePullPolicy: "!IfNotPresent"
If image has tag latest then, imagePullPolicy cannot be IfNotPresent. |
+| Equality | ~() | if tag is specified, then it should have the provided value.
e.g.
~(hostPath):
path: "!/var/lib"
If hostPath is defined then the path cannot be /var/lib |
+| Existance | ^() | It can be specified on the list/array type only. If there exists at least one resource in the list that satisfies the pattern.
e.g.
^(containers):
- image: nginx:latest
There must exist at least one container with image nginx:latest. |
## Example
The next rule prevents the creation of Deployment, StatefuleSet and DaemonSet resources without label 'app' in selector:
````yaml
diff --git a/examples/best_practices/policy_validate_host_path.yaml b/examples/best_practices/policy_validate_host_path.yaml
index 9a62a35e8d..b34529cdee 100644
--- a/examples/best_practices/policy_validate_host_path.yaml
+++ b/examples/best_practices/policy_validate_host_path.yaml
@@ -14,5 +14,5 @@ spec:
pattern:
spec:
volumes:
- - (hostPath):
+ - ~(hostPath):
path: "!/var/lib"
diff --git a/examples/best_practices/resources/resource_validate_image_tag_latest_deny.yaml b/examples/best_practices/resources/resource_validate_image_tag_latest_deny.yaml
index 5007340862..904f3719e0 100644
--- a/examples/best_practices/resources/resource_validate_image_tag_latest_deny.yaml
+++ b/examples/best_practices/resources/resource_validate_image_tag_latest_deny.yaml
@@ -7,5 +7,4 @@ metadata:
spec:
containers:
- name: nginx
- image: nginx:latest
- imagePullPolicy: NotPresent
\ No newline at end of file
+ image: nginx:latest
\ No newline at end of file
diff --git a/pkg/engine/anchor.go b/pkg/engine/anchor.go
index 8af37b5e3e..a2b3775305 100644
--- a/pkg/engine/anchor.go
+++ b/pkg/engine/anchor.go
@@ -19,11 +19,44 @@ func CreateElementHandler(element string, pattern interface{}, path string) Vali
return NewConditionAnchorHandler(element, pattern, path)
case isExistanceAnchor(element):
return NewExistanceHandler(element, pattern, path)
+ case isEqualityAnchor(element):
+ return NewEqualityHandler(element, pattern, path)
default:
return NewDefaultHandler(element, pattern, path)
}
}
+func NewEqualityHandler(anchor string, pattern interface{}, path string) ValidationHandler {
+ return EqualityHandler{
+ anchor: anchor,
+ pattern: pattern,
+ path: path,
+ }
+}
+
+//EqualityHandler provides handler for non anchor element
+type EqualityHandler struct {
+ anchor string
+ pattern interface{}
+ path string
+}
+
+//Handle processed condition anchor
+func (eh EqualityHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
+ anchorKey := removeAnchor(eh.anchor)
+ currentPath := eh.path + anchorKey + "/"
+ // check if anchor is present in resource
+ if value, ok := resourceMap[anchorKey]; ok {
+ // validate the values of the pattern
+ returnPath, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
+ if err != nil {
+ return returnPath, err
+ }
+ return "", nil
+ }
+ return "", nil
+}
+
//NewDefaultHandler returns handler for non anchor elements
func NewDefaultHandler(element string, pattern interface{}, path string) ValidationHandler {
return DefaultHandler{
@@ -159,7 +192,7 @@ func getAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]i
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
- if isConditionAnchor(key) || isExistanceAnchor(key) {
+ if isConditionAnchor(key) || isExistanceAnchor(key) || isEqualityAnchor(key) {
anchors[key] = value
continue
}
diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go
index 10b35ecfef..927c4f2833 100644
--- a/pkg/engine/utils.go
+++ b/pkg/engine/utils.go
@@ -295,6 +295,16 @@ func isExistanceAnchor(str string) bool {
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
+func isEqualityAnchor(str string) bool {
+ left := "~("
+ right := ")"
+ if len(str) < len(left)+len(right) {
+ return false
+ }
+ //TODO: trim spaces ?
+ return (str[:len(left)] == left && str[len(str)-len(right):] == right)
+}
+
func isAddingAnchor(key string) bool {
const left = "+("
const right = ")"
@@ -330,7 +340,7 @@ func removeAnchor(key string) string {
return key[1 : len(key)-1]
}
- if isExistanceAnchor(key) || isAddingAnchor(key) {
+ if isExistanceAnchor(key) || isAddingAnchor(key) || isEqualityAnchor(key) {
return key[2 : len(key)-1]
}
diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go
index 12ccaf9e7a..650b1fd2ff 100644
--- a/pkg/engine/validation.go
+++ b/pkg/engine/validation.go
@@ -193,17 +193,20 @@ func validateMap(resourceMap, patternMap map[string]interface{}, origPattern int
// Evaluate anchors
for key, patternElement := range anchors {
// get handler for each pattern in the pattern
- // - Anchor
+ // - Conditional
// - Existance
+ // - Equality
handler := CreateElementHandler(key, patternElement, path)
handlerPath, err := handler.Handle(resourceMap, origPattern)
// if there are resource values at same level, then anchor acts as conditional instead of a strict check
// but if there are non then its a if then check
if err != nil {
- if len(resources) == 0 {
- return handlerPath, err
+ // If Conditional anchor fails then we dont process the resources
+ if isConditionAnchor(key) {
+ glog.V(4).Infof("condition anchor did not satisfy, wont process the resources: %s", err)
+ return "", nil
}
- return "", nil
+ return handlerPath, err
}
}
// Evaluate resources
diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go
index cc02abc5d1..839668526f 100644
--- a/pkg/engine/validation_test.go
+++ b/pkg/engine/validation_test.go
@@ -1816,15 +1816,14 @@ func TestValidate_image_tag_fail(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
- // msgs := []string{
- // "Validation rule 'validate-tag' failed at '/spec/containers/0/image/' for resource Pod//myapp-pod. An image tag is required",
- // "Validation rule 'validate-latest' succesfully validated",
- // }
+ msgs := []string{
+ "Validation rule 'validate-tag' succesfully validated",
+ "Validation rule 'validate-latest' failed at '/spec/containers/0/imagePullPolicy/' for resource Pod//myapp-pod. imagePullPolicy 'Always' required with tag 'latest'",
+ }
er := Validate(policy, *resourceUnstructured)
- // for _, r := range er.PolicyResponse.Rules {
- // t.Log(r.Message)
- // // assert.Equal(t, r.Message, msgs[index])
- // }
+ for index, r := range er.PolicyResponse.Rules {
+ assert.Equal(t, r.Message, msgs[index])
+ }
assert.Assert(t, !er.IsSuccesful())
}
@@ -1915,15 +1914,14 @@ func TestValidate_image_tag_pass(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
- // msgs := []string{
- // "Validation rule 'validate-tag' failed at '/spec/containers/0/image/' for resource Pod//myapp-pod. An image tag is required",
- // "Validation rule 'validate-latest' succesfully validated",
- // }
+ msgs := []string{
+ "Validation rule 'validate-tag' succesfully validated",
+ "Validation rule 'validate-latest' succesfully validated",
+ }
er := Validate(policy, *resourceUnstructured)
- // for _, r := range er.PolicyResponse.Rules {
- // t.Log(r.Message)
- // // assert.Equal(t, r.Message, msgs[index])
- // }
+ for index, r := range er.PolicyResponse.Rules {
+ assert.Equal(t, r.Message, msgs[index])
+ }
assert.Assert(t, er.IsSuccesful())
}
@@ -2109,7 +2107,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) {
"volumes": [
{
"name": "*",
- "(hostPath)": {
+ "~(hostPath)": {
"path": "!/var/lib"
}
}
@@ -2197,7 +2195,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) {
"spec": {
"volumes": [
{
- "(hostPath)": {
+ "~(hostPath)": {
"path": "!/var/lib"
}
}
@@ -2283,7 +2281,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) {
"message": "pod: validate run as non root user",
"pattern": {
"spec": {
- "(securityContext)": {
+ "~(securityContext)": {
"runAsNonRoot": true
}
}
@@ -2352,7 +2350,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) {
"message": "pod: validate run as non root user",
"pattern": {
"spec": {
- "(securityContext)": {
+ "~(securityContext)": {
"runAsNonRoot": true
}
}
@@ -2424,7 +2422,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) {
"message": "pod: validate run as non root user",
"pattern": {
"spec": {
- "(securityContext)": {
+ "~(securityContext)": {
"runAsNonRoot": true
}
}
@@ -2496,7 +2494,7 @@ func TestValidate_AnchorList_pass(t *testing.T) {
"validate": {
"pattern": {
"spec": {
- "(containers)": [
+ "~(containers)": [
{
"name": "nginx"
}
@@ -2539,12 +2537,12 @@ func TestValidate_AnchorList_pass(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
- // msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
+ msgs := []string{"Validation rule 'pod image rule' succesfully validated"}
- // for _, r := range er.PolicyResponse.Rules {
- // // t.Error(r.Message)
- // // assert.Equal(t, r.Message, msgs[index])
- // }
+ for index, r := range er.PolicyResponse.Rules {
+ t.Log(r.Message)
+ assert.Equal(t, r.Message, msgs[index])
+ }
assert.Assert(t, er.IsSuccesful())
}
@@ -2570,7 +2568,7 @@ func TestValidate_AnchorList_fail(t *testing.T) {
"validate": {
"pattern": {
"spec": {
- "(containers)": [
+ "~(containers)": [
{
"name": "nginx"
}
@@ -2614,8 +2612,8 @@ func TestValidate_AnchorList_fail(t *testing.T) {
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
// msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/1/name/' for resource Pod//myapp-pod."}
-
// for index, r := range er.PolicyResponse.Rules {
+ // // t.Log(r.Message)
// assert.Equal(t, r.Message, msgs[index])
// }
assert.Assert(t, !er.IsSuccesful())
@@ -2687,10 +2685,10 @@ func TestValidate_existenceAnchor_fail(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
- // msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/' for resource Pod//myapp-pod"}
+ // msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/' for resource Pod//myapp-pod."}
// for index, r := range er.PolicyResponse.Rules {
- // // t.Error(r.Message)
+ // t.Log(r.Message)
// assert.Equal(t, r.Message, msgs[index])
// }
assert.Assert(t, !er.IsSuccesful())
@@ -2762,11 +2760,10 @@ func TestValidate_existenceAnchor_pass(t *testing.T) {
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
er := Validate(policy, *resourceUnstructured)
- // msgs := []string{"Validation rule 'pod rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user"}
+ msgs := []string{"Validation rule 'pod image rule' succesfully validated"}
- // for _, r := range er.PolicyResponse.Rules {
- // t.Error(r.Message)
- // // assert.Equal(t, r.Message, msgs[index])
- // }
+ for index, r := range er.PolicyResponse.Rules {
+ assert.Equal(t, r.Message, msgs[index])
+ }
assert.Assert(t, er.IsSuccesful())
}