mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-08 10:04:25 +00:00
Merge commit 'cfbd2120938b8a7f81f4a9c325fa3f6e816d2bf1' into 158_array_validation
This commit is contained in:
commit
3fbb9f8a35
40 changed files with 417 additions and 284 deletions
|
@ -34,15 +34,53 @@ 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
|
||||
|
||||
Anchors allow conditional processing (i.e. "if-then-else) and other logical checks in validation patterns. The following types of anchors are supported:
|
||||
|
||||
|
||||
| Anchor | Tag | Behavior |
|
||||
|------------- |----- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Conditional | () | If tag with the given value is specified, then following resource elements must satisfy the conditions. <br/>e.g. If image has tag latest then imagePullPolicy cannot be IfNotPresent. <br/> (image): "*:latest" <br> imagePullPolicy: "!IfNotPresent"<br/> |
|
||||
| Equality | =() | If tag is specified, then it should have the provided value. <br/>e.g. If hostPath is defined then the path cannot be /var/lib<br/> =(hostPath):<br/> path: "!/var/lib"<br/> |
|
||||
| Existence | ^() | Works on the list/array type only. If at least one element in the satisfies the pattern. <br/>e.g. At least one container with image nginx:latest must exist. <br/> ^(containers):<br/> - image: nginx:latest<br/> |
|
||||
| Conditional | () | If tag with the given value (including child elements) is specified, then peer elements will be processed. <br/>e.g. If image has tag latest then imagePullPolicy cannot be IfNotPresent. <br/> (image): "*:latest" <br> imagePullPolicy: "!IfNotPresent"<br/> |
|
||||
| Equality | =() | If tag is specified, then processing continues. For tags with scalar values, the value must match. For tags with child elements, the child element is further evaluated as a validation pattern. <br/>e.g. If hostPath is defined then the path cannot be /var/lib<br/> =(hostPath):<br/> path: "!/var/lib"<br/> |
|
||||
| Existence | ^() | Works on the list/array type only. If at least one element in the list satisfies the pattern. In contrast, a conditional anchor would validate that all elements in the list match the pattern. <br/>e.g. At least one container with image nginx:latest must exist. <br/> ^(containers):<br/> - image: nginx:latest<br/> |
|
||||
| Negation | X() | The tag cannot be specified. The value of the tag is not evaulated. <br/>e.g. Hostpath tag cannot be defined.<br/> X(hostPath):<br/> |
|
||||
|
||||
## Example
|
||||
## Anchors and child elements
|
||||
|
||||
Child elements are handled differently for conditional and equality anchors.
|
||||
|
||||
For conditional anchors, the child element is considered to be part of the "if" clause, and all peer elements are considered to be part of the "then" clause. For example, consider the pattern:
|
||||
|
||||
````yaml
|
||||
pattern:
|
||||
spec:
|
||||
metadata:
|
||||
labels:
|
||||
allow-docker: true
|
||||
(volumes):
|
||||
(hostPath):
|
||||
path: "/var/run/docker.sock"
|
||||
````
|
||||
|
||||
This reads as "If a hostPath volume exists and the path it equals /var/run/docker.sock, then a label "allow-docker" must be specified with a value of true."
|
||||
|
||||
For equality anchors, a child element is considered to be part of the "then" clause. Consider this pattern:
|
||||
|
||||
````yaml
|
||||
pattern:
|
||||
spec:
|
||||
=(volumes):
|
||||
=(hostPath):
|
||||
path: "!/var/run/docker.sock"
|
||||
````
|
||||
|
||||
This is read as "If a hostPath volume exists, then the path must not be equal to /var/run/docker.sock".
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
The following rule prevents the creation of Deployment, StatefuleSet and DaemonSet resources without label 'app' in selector:
|
||||
|
||||
````yaml
|
||||
|
||||
apiVersion : kyverno.io/v1alpha1
|
||||
|
@ -75,10 +113,11 @@ spec :
|
|||
|
||||
````
|
||||
|
||||
### Check if one exist
|
||||
A variation of an anchor, is to check existance of one element. This is done by using the ^(...) notation for the field.
|
||||
### Existence anchor: at least one
|
||||
|
||||
For example, this pattern will check the existance of "name" field in the list:
|
||||
A variation of an anchor, is to check that in a list of elements at least one element exists that matches the patterm. This is done by using the ^(...) notation for the field.
|
||||
|
||||
For example, this pattern will check that at least one container has memory requests and limits defined and that the request is less than the limit:
|
||||
|
||||
````yaml
|
||||
apiVersion : kyverno.io/v1alpha1
|
||||
|
@ -101,7 +140,6 @@ spec :
|
|||
pattern:
|
||||
spec:
|
||||
^(containers):
|
||||
- (name): "*"
|
||||
resources:
|
||||
requests:
|
||||
memory: "$(<=./../../limits/memory)"
|
||||
|
@ -109,11 +147,13 @@ spec :
|
|||
memory: "2048Mi"
|
||||
````
|
||||
|
||||
### Allow OR across overlay pattern
|
||||
In some cases one content can be defined at a different level. For example, a security context can be defined at the Pod or Container level. The validation rule should pass if one of the conditions is met.
|
||||
`anyPattern` can be used to check on at least one of condition, it is the array of pattern, and the rule will be passed if at least one pattern is true.
|
||||
### Logical OR across validation patterns
|
||||
|
||||
<small>*Note: either `pattern` or `anyPattern` is allowed in each rule, they can't be decalred in the same rule.*</small>
|
||||
In some cases content can be defined at a different level. For example, a security context can be defined at the Pod or Container level. The validation rule should pass if either one of the conditions is met.
|
||||
|
||||
The `anyPattern` tag can be used to check if any one of the patterns in the list match.
|
||||
|
||||
<small>*Note: either one of `pattern` or `anyPattern` is allowed in a rule, they both can't be declared in the same rule.*</small>
|
||||
|
||||
````yaml
|
||||
apiVersion: kyverno.io/v1alpha1
|
||||
|
@ -145,7 +185,7 @@ spec:
|
|||
````
|
||||
|
||||
|
||||
Additional examples are available in [examples](/examples/)
|
||||
Additional examples are available in [samples](/samples/README.md)
|
||||
|
||||
|
||||
---
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
@ -9,7 +8,6 @@ import (
|
|||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -149,38 +147,12 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, rul
|
|||
|
||||
//checkResource checks if the config is present in th eresource
|
||||
func checkResource(config interface{}, resource *unstructured.Unstructured) (bool, error) {
|
||||
var err error
|
||||
|
||||
objByte, err := resource.MarshalJSON()
|
||||
// we are checking if config is a subset of resource with default pattern
|
||||
path, err := validateResourceWithPattern(resource.Object, config)
|
||||
if err != nil {
|
||||
// unable to parse the json
|
||||
glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err)
|
||||
return false, err
|
||||
}
|
||||
err = resource.UnmarshalJSON(objByte)
|
||||
if err != nil {
|
||||
// unable to parse the json
|
||||
return false, err
|
||||
}
|
||||
// marshall and unmarshall json to verify if its right format
|
||||
configByte, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
// unable to marshall the config
|
||||
return false, err
|
||||
}
|
||||
var configData interface{}
|
||||
err = json.Unmarshal(configByte, &configData)
|
||||
if err != nil {
|
||||
// unable to unmarshall
|
||||
return false, err
|
||||
}
|
||||
|
||||
var objData interface{}
|
||||
err = json.Unmarshal(objByte, &objData)
|
||||
if err != nil {
|
||||
// unable to unmarshall
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the config is a subset of resource
|
||||
return utils.JSONsubsetValue(configData, objData), nil
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package engine
|
|||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
@ -91,6 +92,14 @@ func validateValueWithIntPattern(value interface{}, pattern int64) bool {
|
|||
|
||||
glog.Warningf("Expected int, found float: %f\n", typedValue)
|
||||
return false
|
||||
case string:
|
||||
// extract int64 from string
|
||||
int64Num, err := strconv.ParseInt(typedValue, 10, 64)
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to parse int64 from string: %v", err)
|
||||
return false
|
||||
}
|
||||
return int64Num == pattern
|
||||
default:
|
||||
glog.Warningf("Expected int, found: %T\n", value)
|
||||
return false
|
||||
|
@ -105,11 +114,25 @@ func validateValueWithFloatPattern(value interface{}, pattern float64) bool {
|
|||
if pattern == math.Trunc(pattern) {
|
||||
return int(pattern) == value
|
||||
}
|
||||
|
||||
glog.Warningf("Expected float, found int: %d\n", typedValue)
|
||||
return false
|
||||
case int64:
|
||||
// check that float has no fraction
|
||||
if pattern == math.Trunc(pattern) {
|
||||
return int64(pattern) == value
|
||||
}
|
||||
glog.Warningf("Expected float, found int: %d\n", typedValue)
|
||||
return false
|
||||
case float64:
|
||||
return typedValue == pattern
|
||||
case string:
|
||||
// extract float64 from string
|
||||
float64Num, err := strconv.ParseFloat(typedValue, 64)
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to parse float64 from string: %v", err)
|
||||
return false
|
||||
}
|
||||
return float64Num == pattern
|
||||
default:
|
||||
glog.Warningf("Expected float, found: %T\n", value)
|
||||
return false
|
||||
|
|
|
@ -87,7 +87,7 @@ func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (re
|
|||
// 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)
|
||||
response.Success = false
|
||||
response.Message = fmt.Sprintf("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message)
|
||||
response.Message = fmt.Sprintf("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s.", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message)
|
||||
return response
|
||||
}
|
||||
// rule application succesful
|
||||
|
@ -122,7 +122,7 @@ func validatePatterns(resource unstructured.Unstructured, rule kyverno.Rule) (re
|
|||
response.Success = false
|
||||
response.Success = false
|
||||
var errorStr []string
|
||||
errorStr = append(errorStr, fmt.Sprintf("Validation rule '%s' failed to validate patterns defined in anyPattern. %s", rule.Name, rule.Validation.Message))
|
||||
errorStr = append(errorStr, fmt.Sprintf("Validation rule '%s' failed to validate patterns defined in anyPattern. %s.", rule.Name, rule.Validation.Message))
|
||||
for index, err := range errs {
|
||||
glog.V(4).Infof("anyPattern[%d] failed at path %s: %v", index, failedPaths[index], err)
|
||||
str := fmt.Sprintf("anyPattern[%d] failed at path %s", index, failedPaths[index])
|
||||
|
|
|
@ -1818,7 +1818,7 @@ func TestValidate_image_tag_fail(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
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'",
|
||||
"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 index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -1992,7 +1992,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {
|
|||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
msgs := []string{"Validation rule 'check-default-namespace' failed to validate patterns defined in anyPattern. A namespace is required; anyPattern[0] failed at path /metadata/namespace/; anyPattern[1] failed at path /metadata/namespace/"}
|
||||
msgs := []string{"Validation rule 'check-default-namespace' failed to validate patterns defined in anyPattern. A namespace is required.; anyPattern[0] failed at path /metadata/namespace/; anyPattern[1] failed at path /metadata/namespace/"}
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
}
|
||||
|
@ -2073,7 +2073,7 @@ func TestValidate_host_network_port(t *testing.T) {
|
|||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
msgs := []string{"Validation rule 'validate-host-network-port' failed at '/spec/containers/0/ports/0/hostPort/' for resource Pod//nginx-host-network. Host network and port are not allowed"}
|
||||
msgs := []string{"Validation rule 'validate-host-network-port' failed at '/spec/containers/0/ports/0/hostPort/' for resource Pod//nginx-host-network. Host network and port are not allowed."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
|
@ -2250,7 +2250,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) {
|
|||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/path/' for resource Pod//image-with-hostpath. Host path '/var/lib/' is not allowed"}
|
||||
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/path/' for resource Pod//image-with-hostpath. Host path '/var/lib/' is not allowed."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
|
@ -2463,7 +2463,7 @@ func TestValidate_anchor_map_found_invalid(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 rule 2' failed at '/spec/securityContext/runAsNonRoot/' for resource Pod//myapp-pod. pod: validate run as non root user."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
|
@ -2847,7 +2847,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) {
|
|||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed"}
|
||||
msgs := []string{"Validation rule 'validate-host-path' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
|
|
|
@ -115,3 +115,14 @@ func Test_validate_disallow_host_filesystem_fail(t *testing.T) {
|
|||
func Test_validate_disallow_host_filesystem_pass(t *testing.T) {
|
||||
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_host_filesystem_pass.yaml")
|
||||
}
|
||||
func Test_validate_disallow_new_capabilities(t *testing.T) {
|
||||
testScenario(t, "/test/scenarios/samples/best_practices/scenario_validate_disallow_new_capabilities.yaml")
|
||||
}
|
||||
|
||||
func Test_validate_disallow_docker_sock_mount(t *testing.T) {
|
||||
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_docker_sock_mount.yaml")
|
||||
}
|
||||
|
||||
func Test_validate_disallow_helm_tiller(t *testing.T) {
|
||||
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml")
|
||||
}
|
||||
|
|
|
@ -1,193 +1,5 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
//JSONsubsetValue checks if JSON a is contained in JSON b
|
||||
func JSONsubsetValue(a interface{}, b interface{}) bool {
|
||||
switch typed := a.(type) {
|
||||
case bool:
|
||||
bv, ok := b.(bool)
|
||||
if !ok {
|
||||
glog.Errorf("expected bool found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(bool)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
case int:
|
||||
bv, ok := b.(int)
|
||||
if !ok {
|
||||
glog.Errorf("expected int found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(int)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
bv, ok := b.(float64)
|
||||
if !ok {
|
||||
glog.Errorf("expected float64 found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(float64)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
|
||||
case string:
|
||||
bv, ok := b.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected string found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(string)
|
||||
if av == bv {
|
||||
return true
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
bv, ok := b.(map[string]interface{})
|
||||
if !ok {
|
||||
glog.Errorf("expected map[string]interface{} found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.(map[string]interface{})
|
||||
return subsetMap(av, bv)
|
||||
case []interface{}:
|
||||
// TODO: verify the logic
|
||||
bv, ok := b.([]interface{})
|
||||
if !ok {
|
||||
glog.Errorf("expected []interface{} found %T", b)
|
||||
return false
|
||||
}
|
||||
av, _ := a.([]interface{})
|
||||
return subsetSlice(av, bv)
|
||||
default:
|
||||
glog.Errorf("Unspported type %s", typed)
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func subsetMap(a, b map[string]interface{}) bool {
|
||||
// check if keys are present
|
||||
for k := range a {
|
||||
if _, ok := b[k]; !ok {
|
||||
glog.Errorf("key %s, not present in resource", k)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// check if values for the keys match
|
||||
for ak, av := range a {
|
||||
bv := b[ak]
|
||||
if !JSONsubsetValue(av, bv) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func containsInt(a interface{}, b []interface{}) bool {
|
||||
switch typed := a.(type) {
|
||||
case bool:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(bool)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(bool)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case int:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(int)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(int)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case float64:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(float64)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(float64)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case string:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(string)
|
||||
|
||||
if bv == av {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case map[string]interface{}:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.(map[string]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.(map[string]interface{})
|
||||
if subsetMap(av, bv) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, bv := range b {
|
||||
bv, ok := bv.([]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
av, _ := a.([]interface{})
|
||||
if JSONsubsetValue(av, bv) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
default:
|
||||
glog.Errorf("Unspported type %s", typed)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func subsetSlice(a, b []interface{}) bool {
|
||||
// if empty
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
// check if len is not greater
|
||||
if len(a) > len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, av := range a {
|
||||
if !containsInt(av, b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
|
||||
func JoinPatches(patches [][]byte) []byte {
|
||||
var result []byte
|
||||
|
|
|
@ -90,8 +90,6 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pat
|
|||
glog.V(2).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
|
||||
|
||||
// glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||
|
||||
engineResponse := engine.Validate(*policy, *resource)
|
||||
engineResponses = append(engineResponses, engineResponse)
|
||||
// Gather policy application statistics
|
||||
|
|
35
samples/DisallowDockerSockMount.md
Normal file
35
samples/DisallowDockerSockMount.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Disallow Docker socket bind mount
|
||||
|
||||
The Docker socket bind mount allows access to the
|
||||
Docker daemon on the node. This access can be used for privilege escalation and
|
||||
to manage containers outside of Kubernetes, and hence should not be allowed.
|
||||
|
||||
## Policy YAML
|
||||
|
||||
[disallow_docker_sock_mount.yaml](best_practices/disallow_docker_sock_mount.yaml)
|
||||
|
||||
````yaml
|
||||
apiVersion: kyverno.io/v1alpha1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-docker-sock-mount
|
||||
annotations:
|
||||
policies.kyverno.io/category: Security
|
||||
policies.kyverno.io/description: The Docker socket bind mount allows access to the
|
||||
Docker daemon on the node. This access can be used for privilege escalation and
|
||||
to manage containers outside of Kubernetes, and hence should not be allowed.
|
||||
spec:
|
||||
rules:
|
||||
- name: validate-docker-sock-mount
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Use of the Docker Unix socket is not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
=(volumes):
|
||||
=(hostPath):
|
||||
path: "!/var/run/docker.sock"
|
||||
````
|
30
samples/DisallowHelmTiller.md
Normal file
30
samples/DisallowHelmTiller.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Disallow Helm Tiller
|
||||
|
||||
Tiller has known security challenges. It requires adminstrative privileges and acts as a shared resource accessible to any authenticated user. Tiller can lead to privilge escalation as restricted users can impact other users.
|
||||
|
||||
## Policy YAML
|
||||
|
||||
````yaml
|
||||
apiVersion : kyverno.io/v1alpha1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-helm-tiller
|
||||
annotations:
|
||||
policies.kyverno.io/category: Security
|
||||
policies.kyverno.io/description:
|
||||
spec:
|
||||
rules:
|
||||
- name: validate-helm-tiller
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Helm Tiller is not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
image: "!*tiller*"
|
||||
|
||||
````
|
|
@ -21,7 +21,7 @@ spec:
|
|||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Defining hostNetwork and hostPort are not allowed."
|
||||
message: "Defining hostNetwork and hostPort are not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
(hostNetwork): false
|
||||
|
|
45
samples/DisallowNewCapabilities.md
Normal file
45
samples/DisallowNewCapabilities.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Disallow new capabilities
|
||||
|
||||
Linux allows defining fine-grained permissions using
|
||||
capabilities. With Kubernetes, it is possible to add capabilities that escalate the
|
||||
level of kernel access and allow other potentially dangerous behaviors. This policy
|
||||
enforces that pods cannot add new capabilities. Other policies can be used to set
|
||||
default capabilities.
|
||||
|
||||
## Policy YAML
|
||||
|
||||
[disallow_new_capabilities.yaml](best_practices/disallow_new_capabilities.yaml)
|
||||
|
||||
````yaml
|
||||
apiVersion: kyverno.io/v1alpha1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: validate-new-capabilities
|
||||
annotations:
|
||||
policies.kyverno.io/category: Security Context
|
||||
policies.kyverno.io/description: Linux allows defining fine-grained permissions using
|
||||
capabilities. With Kubernetes, it is possible to add capabilities that escalate the
|
||||
level of kernel access and allow other potentially dangerous behaviors. This policy
|
||||
enforces that pods cannot add new capabilities. Other policies can be used to set
|
||||
default capabilities.
|
||||
spec:
|
||||
rules:
|
||||
- name: deny-new-capabilities
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Capabilities cannot be added"
|
||||
anyPattern:
|
||||
- spec:
|
||||
=(securityContext):
|
||||
=(capabilities):
|
||||
X(add): null
|
||||
- spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
=(securityContext):
|
||||
=(capabilities):
|
||||
X(add): null
|
||||
````
|
|
@ -38,27 +38,29 @@ These policies are highly recommended.
|
|||
|
||||
1. [Run as non-root user](RunAsNonRootUser.md)
|
||||
2. [Disable privileged containers and disallow privilege escalation](DisablePrivilegedContainers.md)
|
||||
3. [Require Read-only root filesystem](RequireReadOnlyFS.md)
|
||||
4. [Disallow use of bind mounts (`hostPath` volumes)](DisallowHostFS.md)
|
||||
5. [Disallow `hostNetwork` and `hostPort`](DisallowHostNetworkPort.md)
|
||||
6. [Disallow `hostPID` and `hostIPC`](DisallowHostPIDIPC.md)
|
||||
7. [Disallow unknown image registries](DisallowUnknownRegistries.md)
|
||||
8. [Disallow latest image tag](DisallowLatestTag.md)
|
||||
9. [Disallow use of default namespace](DisallowDefaultNamespace.md)
|
||||
10. [Require namespace limits and quotas](RequireNSLimitsQuotas.md)
|
||||
11. [Require pod resource requests and limits](RequirePodRequestsLimits.md)
|
||||
12. [Require pod `livenessProbe` and `readinessProbe`](RequirePodProbes.md)
|
||||
13. [Default deny all ingress traffic](DefaultDenyAllIngress.md)
|
||||
|
||||
3. [Disallow new capabilities](DisallowNewCapabilities.md)
|
||||
4. [Require Read-only root filesystem](RequireReadOnlyFS.md)
|
||||
5. [Disallow use of bind mounts (`hostPath` volumes)](DisallowHostFS.md)
|
||||
6. [Disallow docker socket bind mount](DisallowDockerSockMount.md)
|
||||
7. [Disallow `hostNetwork` and `hostPort`](DisallowHostNetworkPort.md)
|
||||
8. [Disallow `hostPID` and `hostIPC`](DisallowHostPIDIPC.md)
|
||||
9. [Disallow unknown image registries](DisallowUnknownRegistries.md)
|
||||
10. [Disallow latest image tag](DisallowLatestTag.md)
|
||||
11. [Disallow use of default namespace](DisallowDefaultNamespace.md)
|
||||
12. [Require namespace limits and quotas](RequireNSLimitsQuotas.md)
|
||||
13. [Require pod resource requests and limits](RequirePodRequestsLimits.md)
|
||||
14. [Require pod `livenessProbe` and `readinessProbe`](RequirePodProbes.md)
|
||||
15. [Default deny all ingress traffic](DefaultDenyAllIngress.md)
|
||||
16. [Disallow Helm Tiller](DisallowHelmTiller.md)
|
||||
|
||||
## Additional Policies
|
||||
|
||||
The policies provide additional best practices and are worthy of close consideration. These policies may require workload specific changes.
|
||||
|
||||
14. [Limit use of `NodePort` services](LimitNodePort.md)
|
||||
15. [Limit automount of Service Account credentials](DisallowAutomountSACredentials.md)
|
||||
16. [Configure Linux Capabilities](AssignLinuxCapabilities.md)
|
||||
17. [Limit Kernel parameter access](ConfigureKernelParmeters.md)
|
||||
16. [Limit use of `NodePort` services](LimitNodePort.md)
|
||||
17. [Limit automount of Service Account credentials](DisallowAutomountSACredentials.md)
|
||||
18. [Configure Linux Capabilities](AssignLinuxCapabilities.md)
|
||||
19. [Limit Kernel parameter access](ConfigureKernelParmeters.md)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ spec:
|
|||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Root user is not allowed. Set runAsNonRoot to true."
|
||||
message: "Root user is not allowed. Set runAsNonRoot to true"
|
||||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
|
|
|
@ -16,7 +16,7 @@ spec:
|
|||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Root user is not allowed. Set runAsNonRoot to true."
|
||||
message: "Root user is not allowed. Set runAsNonRoot to true"
|
||||
anyPattern:
|
||||
- spec:
|
||||
securityContext:
|
||||
|
|
23
samples/best_practices/disallow_docker_sock_mount.yaml
Normal file
23
samples/best_practices/disallow_docker_sock_mount.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
apiVersion: kyverno.io/v1alpha1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-docker-sock-mount
|
||||
annotations:
|
||||
policies.kyverno.io/category: Security
|
||||
policies.kyverno.io/description: The Docker socket bind mount allows access to the
|
||||
Docker daemon on the node. This access can be used for privilege escalation and
|
||||
to manage containers outside of Kubernetes, and hence should not be allowed.
|
||||
spec:
|
||||
rules:
|
||||
- name: validate-docker-sock-mount
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Use of the Docker Unix socket is not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
=(volumes):
|
||||
=(hostPath):
|
||||
path: "!/var/run/docker.sock"
|
21
samples/best_practices/disallow_helm_tiller.yaml
Normal file
21
samples/best_practices/disallow_helm_tiller.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
apiVersion : kyverno.io/v1alpha1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-helm-tiller
|
||||
annotations:
|
||||
policies.kyverno.io/category: Security
|
||||
policies.kyverno.io/description:
|
||||
spec:
|
||||
rules:
|
||||
- name: validate-helm-tiller
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Helm Tiller is not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
image: "!*tiller*"
|
|
@ -14,7 +14,7 @@ spec:
|
|||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Defining hostNetwork and hostPort are not allowed."
|
||||
message: "Defining hostNetwork and hostPort are not allowed"
|
||||
pattern:
|
||||
spec:
|
||||
(hostNetwork): false
|
||||
|
|
31
samples/best_practices/disallow_new_capabilities.yaml
Normal file
31
samples/best_practices/disallow_new_capabilities.yaml
Normal file
|
@ -0,0 +1,31 @@
|
|||
apiVersion: kyverno.io/v1alpha1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: validate-new-capabilities
|
||||
annotations:
|
||||
policies.kyverno.io/category: Security Context
|
||||
policies.kyverno.io/description: Linux allows defining fine-grained permissions using
|
||||
capabilities. With Kubernetes, it is possible to add capabilities that escalate the
|
||||
level of kernel access and allow other potentially dangerous behaviors. This policy
|
||||
enforces that pods cannot add new capabilities. Other policies can be used to set
|
||||
default capabilities.
|
||||
spec:
|
||||
rules:
|
||||
- name: deny-new-capabilities
|
||||
match:
|
||||
resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Capabilities cannot be added"
|
||||
anyPattern:
|
||||
- spec:
|
||||
=(securityContext):
|
||||
=(capabilities):
|
||||
X(add): null
|
||||
- spec:
|
||||
containers:
|
||||
- name: "*"
|
||||
=(securityContext):
|
||||
=(capabilities):
|
||||
X(add): null
|
|
@ -20,7 +20,7 @@ spec:
|
|||
data:
|
||||
spec:
|
||||
hard:
|
||||
requests.cpu: '4'
|
||||
requests.memory: "16Gi"
|
||||
limits.cpu: '4'
|
||||
limits.memory: "16Gi"
|
||||
requests.cpu: 4
|
||||
requests.memory: 16Gi
|
||||
limits.cpu: 4
|
||||
limits.memory: 16Gi
|
|
@ -20,7 +20,7 @@ spec:
|
|||
pattern:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: '1000'
|
||||
runAsUser: 1000
|
||||
- name: validate-groupid
|
||||
match:
|
||||
resources:
|
||||
|
@ -31,7 +31,7 @@ spec:
|
|||
pattern:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsGroup: '3000'
|
||||
runAsGroup: 3000
|
||||
- name: validate-fsgroup
|
||||
match:
|
||||
resources:
|
||||
|
@ -42,7 +42,7 @@ spec:
|
|||
pattern:
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: '2000'
|
||||
fsGroup: 2000
|
||||
# Alls processes inside the pod can be made to run with specific user and groupID by setting runAsUser and runAsGroup respectively.
|
||||
# fsGroup can be specified to make sure any file created in the volume with have the specified groupID.
|
||||
# The above parameters can also be used in a validate policy to restrict user & group IDs.
|
|
@ -14,7 +14,7 @@ spec:
|
|||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: "Prevent mounting of default service account."
|
||||
message: "Prevent mounting of default service account"
|
||||
pattern:
|
||||
spec:
|
||||
serviceAccountName: "!default"
|
15
test/resources/disallow_docker_sock_mount.yaml
Normal file
15
test/resources/disallow_docker_sock_mount.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-with-docker-sock-mount
|
||||
spec:
|
||||
containers:
|
||||
- name: myshell
|
||||
image: "ubuntu:18.04"
|
||||
command:
|
||||
- /bin/sleep
|
||||
- "300"
|
||||
volumes:
|
||||
- name: dockersock
|
||||
hostPath:
|
||||
path: /var/run/docker.sock
|
8
test/resources/disallow_helm_tiller.yaml
Normal file
8
test/resources/disallow_helm_tiller.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-helm-tiller
|
||||
spec:
|
||||
containers:
|
||||
- name: helm-tiller
|
||||
image: docker.io/tiller:latest
|
15
test/resources/disallow_new_capabilities.yaml
Normal file
15
test/resources/disallow_new_capabilities.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: add-new-capabilities
|
||||
spec:
|
||||
containers:
|
||||
- name: add-new-capabilities
|
||||
image: "ubuntu:18.04"
|
||||
command:
|
||||
- /bin/sleep
|
||||
- "300"
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
|
@ -15,5 +15,5 @@ expected:
|
|||
rules:
|
||||
- name: validate-selinux-options
|
||||
type: Validation
|
||||
message: "Validation rule 'validate-selinux-options' failed at '/spec/containers/0/securityContext/seLinuxOptions/' for resource Pod/default/busybox-selinux. SELinux level is required"
|
||||
message: "Validation rule 'validate-selinux-options' failed at '/spec/containers/0/securityContext/seLinuxOptions/' for resource Pod/default/busybox-selinux. SELinux level is required."
|
||||
success: false
|
|
@ -18,5 +18,5 @@ expected:
|
|||
success: true
|
||||
- name: image-tag-not-latest
|
||||
type: Validation
|
||||
message: "Validation rule 'image-tag-not-latest' failed at '/spec/containers/0/image/' for resource Pod//myapp-pod. Using 'latest' image tag is restricted. Set image tag to a specific version"
|
||||
message: "Validation rule 'image-tag-not-latest' failed at '/spec/containers/0/image/' for resource Pod//myapp-pod. Using 'latest' image tag is restricted. Set image tag to a specific version."
|
||||
success: false
|
||||
|
|
|
@ -16,7 +16,7 @@ expected:
|
|||
rules:
|
||||
- name: check-default-namespace
|
||||
type: Validation
|
||||
message: "Validation rule 'check-default-namespace' failed at '/metadata/namespace/' for resource Pod/default/myapp-pod. Using 'default' namespace is restricted"
|
||||
message: "Validation rule 'check-default-namespace' failed at '/metadata/namespace/' for resource Pod/default/myapp-pod. Using 'default' namespace is restricted."
|
||||
success: false
|
||||
- name: check-namespace-exist
|
||||
type: Validation
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# file path relative to project root
|
||||
input:
|
||||
policy: samples/best_practices/disallow_docker_sock_mount.yaml
|
||||
resource: test/resources/disallow_docker_sock_mount.yaml
|
||||
expected:
|
||||
validation:
|
||||
policyresponse:
|
||||
policy: disallow-docker-sock-mount
|
||||
resource:
|
||||
kind: Pod
|
||||
apiVersion: v1
|
||||
namespace: ''
|
||||
name: pod-with-docker-sock-mount
|
||||
rules:
|
||||
- name: validate-docker-sock-mount
|
||||
type: Validation
|
||||
message: Validation rule 'validate-docker-sock-mount' failed at '/spec/volumes/' for resource Pod//pod-with-docker-sock-mount. Use of the Docker Unix socket is not allowed.
|
||||
success: false
|
|
@ -0,0 +1,16 @@
|
|||
# file paths are relative to project root
|
||||
input:
|
||||
policy: samples/best_practices/disallow_helm_tiller.yaml
|
||||
resource: test/resources/disallow_helm_tiller.yaml
|
||||
expected:
|
||||
validation:
|
||||
policyresponse:
|
||||
policy: disallow-helm-tiller
|
||||
resource:
|
||||
kind: Pod
|
||||
name: pod-helm-tiller
|
||||
rules:
|
||||
- name: validate-helm-tiller
|
||||
type: Validation
|
||||
message: "Validation rule 'validate-helm-tiller' failed at '/spec/containers/0/image/' for resource Pod//pod-helm-tiller. Helm Tiller is not allowed."
|
||||
success: false
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: deny-use-of-host-fs
|
||||
type: Validation
|
||||
message: Validation rule 'deny-use-of-host-fs' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed
|
||||
message: Validation rule 'deny-use-of-host-fs' failed at '/spec/volumes/0/hostPath/' for resource Pod//image-with-hostpath. Host path is not allowed.
|
||||
success: false
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: validate-hostpid-hostipc
|
||||
type: Validation
|
||||
message: Validation rule 'validate-hostpid-hostipc' failed at '/spec/hostIPC/' for resource Pod//nginx-with-hostpid. Disallow use of host's pid namespace and host's ipc namespace
|
||||
message: Validation rule 'validate-hostpid-hostipc' failed at '/spec/hostIPC/' for resource Pod//nginx-with-hostpid. Disallow use of host's pid namespace and host's ipc namespace.
|
||||
success: false
|
|
@ -0,0 +1,18 @@
|
|||
# file path relative to project root
|
||||
input:
|
||||
policy: samples/best_practices/disallow_new_capabilities.yaml
|
||||
resource: test/resources/disallow_new_capabilities.yaml
|
||||
expected:
|
||||
validation:
|
||||
policyresponse:
|
||||
policy: validate-new-capabilities
|
||||
resource:
|
||||
kind: Pod
|
||||
apiVersion: v1
|
||||
namespace: ''
|
||||
name: "add-new-capabilities"
|
||||
rules:
|
||||
- name: deny-new-capabilities
|
||||
type: Validation
|
||||
message: Validation rule 'deny-new-capabilities' failed to validate patterns defined in anyPattern. Capabilities cannot be added.; anyPattern[0] failed at path /spec/; anyPattern[1] failed at path /spec/containers/0/securityContext/capabilities/add/
|
||||
success: false
|
|
@ -13,5 +13,5 @@ expected:
|
|||
rules:
|
||||
- name: disallow-node-port
|
||||
type: Validation
|
||||
message: Validation rule 'disallow-node-port' failed at '/spec/type/' for resource Service//my-service. Disallow service of type NodePort
|
||||
message: Validation rule 'disallow-node-port' failed at '/spec/type/' for resource Service//my-service. Disallow service of type NodePort.
|
||||
success: false
|
|
@ -14,6 +14,6 @@ expected:
|
|||
rules:
|
||||
- name: deny-privileged-priviligedescalation
|
||||
type: Validation
|
||||
message: "Validation rule 'deny-privileged-priviligedescalation' failed to validate patterns defined in anyPattern. Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false; anyPattern[0] failed at path /spec/securityContext/; anyPattern[1] failed at path /spec/containers/0/securityContext/allowPrivilegeEscalation/"
|
||||
message: "Validation rule 'deny-privileged-priviligedescalation' failed to validate patterns defined in anyPattern. Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false.; anyPattern[0] failed at path /spec/securityContext/; anyPattern[1] failed at path /spec/containers/0/securityContext/allowPrivilegeEscalation/"
|
||||
success: false
|
||||
|
||||
|
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: check-probes
|
||||
type: Validation
|
||||
message: Validation rule 'check-probes' failed at '/spec/containers/0/livenessProbe/' for resource Pod//myapp-pod. Liveness and readiness probes are required
|
||||
message: Validation rule 'check-probes' failed at '/spec/containers/0/livenessProbe/' for resource Pod//myapp-pod. Liveness and readiness probes are required.
|
||||
success: false
|
||||
|
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: check-resource-request-limit
|
||||
type: Validation
|
||||
message: Validation rule 'check-resource-request-limit' failed at '/spec/containers/0/resources/limits/cpu/' for resource Pod//myapp-pod. CPU and memory resource requests and limits are required
|
||||
message: Validation rule 'check-resource-request-limit' failed at '/spec/containers/0/resources/limits/cpu/' for resource Pod//myapp-pod. CPU and memory resource requests and limits are required.
|
||||
success: false
|
||||
|
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: validate-readonly-rootfilesystem
|
||||
type: Validation
|
||||
message: Validation rule 'validate-readonly-rootfilesystem' failed at '/spec/containers/0/securityContext/readOnlyRootFilesystem/' for resource Pod//ghost-with-readonly-rootfilesystem. Container require read-only rootfilesystem
|
||||
message: Validation rule 'validate-readonly-rootfilesystem' failed at '/spec/containers/0/securityContext/readOnlyRootFilesystem/' for resource Pod//ghost-with-readonly-rootfilesystem. Container require read-only rootfilesystem.
|
||||
success: false
|
|
@ -15,5 +15,5 @@ expected:
|
|||
rules:
|
||||
- name: validate-container-capablities
|
||||
type: Validation
|
||||
message: "Validation rule 'validate-container-capablities' failed at '/spec/containers/0/securityContext/capabilities/add/0/' for resource Pod//add-capabilities. Allow certain linux capability"
|
||||
message: "Validation rule 'validate-container-capablities' failed at '/spec/containers/0/securityContext/capabilities/add/0/' for resource Pod//add-capabilities. Allow certain linux capability."
|
||||
success: false
|
|
@ -15,5 +15,5 @@ expected:
|
|||
rules:
|
||||
- name: allow-portrange-with-sysctl
|
||||
type: Validation
|
||||
message: "Validation rule 'allow-portrange-with-sysctl' failed at '/spec/securityContext/sysctls/0/value/' for resource Pod//nginx. Allowed port range is from 1024 to 65535"
|
||||
message: "Validation rule 'allow-portrange-with-sysctl' failed at '/spec/securityContext/sysctls/0/value/' for resource Pod//nginx. Allowed port range is from 1024 to 65535."
|
||||
success: false
|
Loading…
Add table
Reference in a new issue