mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-08 10:04:25 +00:00
Merge pull request #358 from nirmata/346_validate_policy
346 validate policy
This commit is contained in:
commit
e02d334dfc
13 changed files with 1067 additions and 154 deletions
|
@ -11,9 +11,9 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "A none 'default' namespace is required"
|
||||
anyPattern:
|
||||
- metadata:
|
||||
namespace: "!default"
|
||||
pattern:
|
||||
metadata:
|
||||
namespace: "!default"
|
||||
- name: check-namespace-exist
|
||||
match:
|
||||
resources:
|
||||
|
@ -21,6 +21,6 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "A namespace is required"
|
||||
anyPattern:
|
||||
- metadata:
|
||||
namespace: "?*"
|
||||
pattern:
|
||||
metadata:
|
||||
namespace: "?*"
|
||||
|
|
|
@ -15,7 +15,7 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "Disallow use of host's pid namespace and host's ipc namespace"
|
||||
anyPattern:
|
||||
- spec:
|
||||
pattern:
|
||||
spec:
|
||||
hostPID: false
|
||||
hostIPC: false
|
||||
|
|
|
@ -15,8 +15,8 @@ spec:
|
|||
- Pod
|
||||
validate:
|
||||
message: "Container should not have read-only rootfilesystem"
|
||||
anyPattern:
|
||||
- spec:
|
||||
container:
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- securityContext:
|
||||
readOnlyRootFilesystem: false
|
|
@ -3,8 +3,8 @@ kind: Pod
|
|||
metadata:
|
||||
name: nginx-with-hostpid
|
||||
spec:
|
||||
hostPID: true
|
||||
hostIPC: false
|
||||
hostPID: false
|
||||
hostIPC: true
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
|
|
|
@ -3,73 +3,8 @@ package v1alpha1
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Validate checks if rule is not empty and all substructures are valid
|
||||
func (r *Rule) Validate() error {
|
||||
// check matches Resoource Description of match resource
|
||||
err := r.MatchResources.ResourceDescription.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if all necesarry fields are present and have values. Also checks a Selector.
|
||||
// Returns error if
|
||||
// - kinds is not defined
|
||||
func (pr *ResourceDescription) Validate() error {
|
||||
if len(pr.Kinds) == 0 {
|
||||
return errors.New("The Kind is not specified")
|
||||
}
|
||||
|
||||
if pr.Selector != nil {
|
||||
selector, err := metav1.LabelSelectorAsSelector(pr.Selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requirements, _ := selector.Requirements()
|
||||
if len(requirements) == 0 {
|
||||
return errors.New("The requirements are not specified in selector")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate if all mandatory PolicyPatch fields are set
|
||||
func (pp *Patch) Validate() error {
|
||||
if pp.Path == "" {
|
||||
return errors.New("JSONPatch field 'path' is mandatory")
|
||||
}
|
||||
|
||||
if pp.Operation == "add" || pp.Operation == "replace" {
|
||||
if pp.Value == nil {
|
||||
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation)
|
||||
}
|
||||
|
||||
return nil
|
||||
} else if pp.Operation == "remove" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
|
||||
}
|
||||
|
||||
// Validate returns error if generator is configured incompletely
|
||||
func (gen *Generation) Validate() error {
|
||||
if gen.Data == nil && gen.Clone == (CloneFrom{}) {
|
||||
return fmt.Errorf("Neither data nor clone (source) of %s is specified", gen.Kind)
|
||||
}
|
||||
if gen.Data != nil && gen.Clone != (CloneFrom{}) {
|
||||
return fmt.Errorf("Both data nor clone (source) of %s are specified", gen.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is declared because k8s:deepcopy-gen is
|
||||
// not able to generate this method for interface{} member
|
||||
func (in *Mutation) DeepCopyInto(out *Mutation) {
|
||||
|
@ -109,3 +44,41 @@ func (rs ResourceSpec) ToKey() string {
|
|||
}
|
||||
return rs.Kind + "." + rs.Namespace + "." + rs.Name
|
||||
}
|
||||
|
||||
// joinErrs joins the list of error into single error
|
||||
// adds a new line between errors
|
||||
func joinErrs(errs []error) error {
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := "\n"
|
||||
for _, err := range errs {
|
||||
res = fmt.Sprintf(res + err.Error() + "\n")
|
||||
}
|
||||
|
||||
return errors.New(res)
|
||||
}
|
||||
|
||||
//Contains Check if strint is contained in a list of string
|
||||
func containString(list []string, element string) bool {
|
||||
for _, e := range list {
|
||||
if e == element {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasExistingAnchor checks if str has existing anchor
|
||||
// strip anchor if necessary
|
||||
func hasExistingAnchor(str string) (bool, string) {
|
||||
left := "^("
|
||||
right := ")"
|
||||
|
||||
if len(str) < len(left)+len(right) {
|
||||
return false, str
|
||||
}
|
||||
|
||||
return (str[:len(left)] == left && str[len(str)-len(right):] == right), str[len(left) : len(str)-len(right)]
|
||||
}
|
||||
|
|
252
pkg/api/kyverno/v1alpha1/validate.go
Normal file
252
pkg/api/kyverno/v1alpha1/validate.go
Normal file
|
@ -0,0 +1,252 @@
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func (p ClusterPolicy) Validate() error {
|
||||
var errs []error
|
||||
|
||||
for _, rule := range p.Spec.Rules {
|
||||
if ruleErrs := rule.Validate(); ruleErrs != nil {
|
||||
errs = append(errs, ruleErrs...)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.ValidateUniqueRuleName(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return joinErrs(errs)
|
||||
}
|
||||
|
||||
// ValidateUniqueRuleName checks if the rule names are unique across a policy
|
||||
func (p ClusterPolicy) ValidateUniqueRuleName() error {
|
||||
var ruleNames []string
|
||||
|
||||
for _, rule := range p.Spec.Rules {
|
||||
if containString(ruleNames, rule.Name) {
|
||||
return fmt.Errorf(`duplicate rule name: '%s'`, rule.Name)
|
||||
}
|
||||
ruleNames = append(ruleNames, rule.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if rule is not empty and all substructures are valid
|
||||
func (r Rule) Validate() []error {
|
||||
var errs []error
|
||||
|
||||
// only one type of rule is allowed per rule
|
||||
if err := r.ValidateRuleType(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// validate resource description block
|
||||
if err := r.MatchResources.ResourceDescription.Validate(true); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := r.ExcludeResources.ResourceDescription.Validate(false); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// validate validation rule
|
||||
if err := r.ValidateOverlayPattern(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if patternErrs := r.ValidateExistingAnchor(); patternErrs != nil {
|
||||
errs = append(errs, patternErrs...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// validateOverlayPattern checks one of pattern/anyPattern must exist
|
||||
func (r Rule) ValidateOverlayPattern() error {
|
||||
if reflect.DeepEqual(r.Validation, Validation{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.Validation.Pattern == nil && len(r.Validation.AnyPattern) == 0 {
|
||||
return fmt.Errorf("neither pattern nor anyPattern found in rule '%s'", r.Name)
|
||||
}
|
||||
|
||||
if r.Validation.Pattern != nil && len(r.Validation.AnyPattern) != 0 {
|
||||
return fmt.Errorf("either pattern or anyPattern is allowed in rule '%s'", r.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRuleType checks only one type of rule is defined per rule
|
||||
func (r Rule) ValidateRuleType() error {
|
||||
mutate := r.HasMutate()
|
||||
validate := r.HasValidate()
|
||||
generate := r.HasGenerate()
|
||||
|
||||
if !mutate && !validate && !generate {
|
||||
return fmt.Errorf("no rule defined in '%s'", r.Name)
|
||||
}
|
||||
|
||||
if (mutate && !validate && !generate) ||
|
||||
(!mutate && validate && !generate) ||
|
||||
(!mutate && !validate && generate) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("multiple types of rule defined in rule '%s', only one type of rule is allowed per rule", r.Name)
|
||||
}
|
||||
|
||||
func (r Rule) HasMutate() bool {
|
||||
return !reflect.DeepEqual(r.Mutation, Mutation{})
|
||||
}
|
||||
|
||||
func (r Rule) HasValidate() bool {
|
||||
return !reflect.DeepEqual(r.Validation, Validation{})
|
||||
}
|
||||
|
||||
func (r Rule) HasGenerate() bool {
|
||||
return !reflect.DeepEqual(r.Generation, Generation{})
|
||||
}
|
||||
|
||||
// Validate checks if all necesarry fields are present and have values. Also checks a Selector.
|
||||
// field type is checked through openapi
|
||||
// Returns error if
|
||||
// - kinds is empty array in matched resource block, i.e. kinds: []
|
||||
// - selector is invalid
|
||||
func (rd ResourceDescription) Validate(matchedResource bool) error {
|
||||
if reflect.DeepEqual(rd, ResourceDescription{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if matchedResource && len(rd.Kinds) == 0 {
|
||||
return errors.New("field Kind is not specified")
|
||||
}
|
||||
|
||||
if rd.Selector != nil {
|
||||
selector, err := metav1.LabelSelectorAsSelector(rd.Selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requirements, _ := selector.Requirements()
|
||||
if len(requirements) == 0 {
|
||||
return errors.New("the requirements are not specified in selector")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate if all mandatory PolicyPatch fields are set
|
||||
func (pp *Patch) Validate() error {
|
||||
if pp.Path == "" {
|
||||
return errors.New("JSONPatch field 'path' is mandatory")
|
||||
}
|
||||
|
||||
if pp.Operation == "add" || pp.Operation == "replace" {
|
||||
if pp.Value == nil {
|
||||
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation)
|
||||
}
|
||||
|
||||
return nil
|
||||
} else if pp.Operation == "remove" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
|
||||
}
|
||||
|
||||
// Validate returns error if generator is configured incompletely
|
||||
func (gen *Generation) Validate() error {
|
||||
if gen.Data == nil && gen.Clone == (CloneFrom{}) {
|
||||
return fmt.Errorf("Neither data nor clone (source) of %s is specified", gen.Kind)
|
||||
}
|
||||
if gen.Data != nil && gen.Clone != (CloneFrom{}) {
|
||||
return fmt.Errorf("Both data nor clone (source) of %s are specified", gen.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateExistingAnchor
|
||||
// existing acnchor must define on array
|
||||
func (r Rule) ValidateExistingAnchor() []error {
|
||||
var errs []error
|
||||
|
||||
if r.Validation.Pattern != nil {
|
||||
if _, err := validateExistingAnchorOnPattern(r.Validation.Pattern, "/"); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Validation.AnyPattern) != 0 {
|
||||
for _, pattern := range r.Validation.AnyPattern {
|
||||
if _, err := validateExistingAnchorOnPattern(pattern, "/"); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// validateExistingAnchorOnPattern validates ^() only defined on array
|
||||
func validateExistingAnchorOnPattern(pattern interface{}, path string) (string, error) {
|
||||
switch typedPattern := pattern.(type) {
|
||||
case map[string]interface{}:
|
||||
return validateMap(typedPattern, path)
|
||||
case []interface{}:
|
||||
return validateArray(typedPattern, path)
|
||||
case string, float64, int, int64, bool, nil:
|
||||
// check on type string
|
||||
if checkedPattern := reflect.ValueOf(pattern); checkedPattern.Kind() == reflect.String {
|
||||
if hasAnchor, str := hasExistingAnchor(checkedPattern.String()); hasAnchor {
|
||||
return path, fmt.Errorf("existing anchor at %s must be of type array, found: %T", path+str, checkedPattern.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
// return nil on all other cases
|
||||
return "", nil
|
||||
default:
|
||||
glog.V(4).Infof("Pattern contains unknown type %T. Path: %s", pattern, path)
|
||||
return path, fmt.Errorf("pattern contains unknown type, path: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
func validateMap(pattern map[string]interface{}, path string) (string, error) {
|
||||
for key, patternElement := range pattern {
|
||||
if hasAnchor, str := hasExistingAnchor(key); hasAnchor {
|
||||
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() != reflect.Slice {
|
||||
return path, fmt.Errorf("existing anchor at %s must be of type array, found: %T", path+str, patternElement)
|
||||
}
|
||||
}
|
||||
|
||||
if path, err := validateExistingAnchorOnPattern(patternElement, path+key+"/"); err != nil {
|
||||
return path, err
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func validateArray(patternArray []interface{}, path string) (string, error) {
|
||||
if len(patternArray) == 0 {
|
||||
return path, fmt.Errorf("pattern array at %s is empty", path)
|
||||
}
|
||||
|
||||
for i, pattern := range patternArray {
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
if path, err := validateExistingAnchorOnPattern(pattern, currentPath); err != nil {
|
||||
return path, err
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
715
pkg/api/kyverno/v1alpha1/validate_test.go
Normal file
715
pkg/api/kyverno/v1alpha1/validate_test.go
Normal file
|
@ -0,0 +1,715 @@
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func Test_Validate_UniqueRuleName(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"validationFailureAction": "audit",
|
||||
"rules": [
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {}
|
||||
},
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var policy *ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = policy.ValidateUniqueRuleName()
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_RuleType_EmptyRule(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mutate": {},
|
||||
"validate": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var policy *ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
err := rule.ValidateRuleType()
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_RuleType_MultipleRule(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mutate": {
|
||||
"overlay": {
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"(name)": "*",
|
||||
"resources": {
|
||||
"limits": {
|
||||
"+(memory)": "300Mi",
|
||||
"+(cpu)": "100"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "validate container security contexts",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy *ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
err := rule.ValidateRuleType()
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_RuleType_SingleRule(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "validate container security contexts",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"securityContext": {
|
||||
"runAsNonRoot": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var policy *ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
err := rule.ValidateRuleType()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_Empty(t *testing.T) {
|
||||
rawResourcedescirption := []byte(`{}`)
|
||||
|
||||
var rd *ResourceDescription
|
||||
err := json.Unmarshal(rawResourcedescirption, &rd)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = rd.Validate(true)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_MissingKindsOnMatched(t *testing.T) {
|
||||
matchedResourcedescirption := []byte(`
|
||||
{
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var rd *ResourceDescription
|
||||
err := json.Unmarshal(matchedResourcedescirption, &rd)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = rd.Validate(true)
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_MissingKindsOnExclude(t *testing.T) {
|
||||
matchedResourcedescirption := []byte(`
|
||||
{
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var rd *ResourceDescription
|
||||
err := json.Unmarshal(matchedResourcedescirption, &rd)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = rd.Validate(false)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_InvalidSelector(t *testing.T) {
|
||||
rawResourcedescirption := []byte(`
|
||||
{
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}`)
|
||||
|
||||
var rd *ResourceDescription
|
||||
err := json.Unmarshal(rawResourcedescirption, &rd)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = rd.Validate(true)
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_Valid(t *testing.T) {
|
||||
rawResourcedescirption := []byte(`
|
||||
{
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
var rd *ResourceDescription
|
||||
err := json.Unmarshal(rawResourcedescirption, &rd)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = rd.Validate(true)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_OverlayPattern_Empty(t *testing.T) {
|
||||
rawRules := []byte(`
|
||||
[
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {}
|
||||
}
|
||||
]`)
|
||||
|
||||
var rules []Rule
|
||||
err := json.Unmarshal(rawRules, &rules)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range rules {
|
||||
err := rule.ValidateOverlayPattern()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_OverlayPattern_Nil_PatternAnypattern(t *testing.T) {
|
||||
rawRules := []byte(`
|
||||
[
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false"
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
var rules []Rule
|
||||
err := json.Unmarshal(rawRules, &rules)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range rules {
|
||||
err := rule.ValidateOverlayPattern()
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_OverlayPattern_Exist_PatternAnypattern(t *testing.T) {
|
||||
rawRules := []byte(`
|
||||
[
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"privileged": false
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "*",
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"privileged": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]`)
|
||||
|
||||
var rules []Rule
|
||||
err := json.Unmarshal(rawRules, &rules)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range rules {
|
||||
err := rule.ValidateOverlayPattern()
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_OverlayPattern_Valid(t *testing.T) {
|
||||
rawRules := []byte(`
|
||||
[
|
||||
{
|
||||
"name": "deny-privileged-disallowpriviligedescalation",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Pod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "Privileged mode is not allowed. Set allowPrivilegeEscalation and privileged to false",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"privileged": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "*",
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"privileged": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]`)
|
||||
|
||||
var rules []Rule
|
||||
err := json.Unmarshal(rawRules, &rules)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range rules {
|
||||
err := rule.ValidateOverlayPattern()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ExistingAnchor_AnchorOnMap(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1alpha1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "container-security-context"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "validate container security contexts",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"^(securityContext)": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
errs := rule.ValidateExistingAnchor()
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ExistingAnchor_AnchorOnString(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1alpha1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "container-security-context"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "validate container security contexts",
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": "^(false)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
errs := rule.ValidateExistingAnchor()
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1alpha1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "container-security-context"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "validate container security contexts",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"^(containers)": [
|
||||
{
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pattern": {
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"^(containers)": [
|
||||
{
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var policy ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
errs := rule.ValidateExistingAnchor()
|
||||
assert.Assert(t, len(errs) == 0)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Validate_Policy(t *testing.T) {
|
||||
rawPolicy := []byte(`
|
||||
{
|
||||
"apiVersion": "kyverno.io/v1alpha1",
|
||||
"kind": "ClusterPolicy",
|
||||
"metadata": {
|
||||
"name": "container-security-context"
|
||||
},
|
||||
"spec": {
|
||||
"rules": [
|
||||
{
|
||||
"name": "validate-user-privilege",
|
||||
"exclude": {
|
||||
"resources": {
|
||||
"namespaces": [
|
||||
"kube-system"
|
||||
]
|
||||
}
|
||||
},
|
||||
"match": {
|
||||
"resources": {
|
||||
"kinds": [
|
||||
"Deployment"
|
||||
],
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.type": "prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"validate": {
|
||||
"message": "validate container security contexts",
|
||||
"anyPattern": [
|
||||
{
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"^(containers)": [
|
||||
{
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
var policy ClusterPolicy
|
||||
err := json.Unmarshal(rawPolicy, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = policy.Validate()
|
||||
assert.NilError(t, err)
|
||||
}
|
|
@ -47,7 +47,7 @@ func (in *ClusterPolicy) DeepCopyInto(out *ClusterPolicy) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ func (in *Policy) DeepCopyInto(out *Policy) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -240,6 +240,11 @@ func (in *Policy) DeepCopy() *Policy {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) {
|
||||
*out = *in
|
||||
if in.Rules != nil {
|
||||
in, out := &in.Rules, &out.Rules
|
||||
*out = make([]RuleStats, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -380,6 +385,22 @@ func (in *Rule) DeepCopy() *Rule {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RuleStats) DeepCopyInto(out *RuleStats) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleStats.
|
||||
func (in *RuleStats) DeepCopy() *RuleStats {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RuleStats)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Spec) DeepCopyInto(out *Spec) {
|
||||
*out = *in
|
||||
|
|
|
@ -60,7 +60,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
|||
MatchExpressions: nil,
|
||||
},
|
||||
}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{resourceDescription}}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||
|
||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
|
|||
MatchExpressions: nil,
|
||||
},
|
||||
}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{resourceDescription}}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||
|
||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
|
|||
MatchExpressions: nil,
|
||||
},
|
||||
}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{resourceDescription}}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||
|
||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{resourceDescription}}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||
|
||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{resourceDescription}}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||
|
||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
||||
}
|
||||
|
@ -386,8 +386,8 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{resourceDescription},
|
||||
ExcludeResources: kyverno.ExcludeResources{resourceDescriptionExclude}}
|
||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
|
||||
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
|
||||
|
||||
assert.Assert(t, !MatchesResourceDescription(*resource, rule))
|
||||
}
|
||||
|
|
|
@ -3,11 +3,9 @@ package webhooks
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -28,13 +26,20 @@ func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionReques
|
|||
Message: fmt.Sprintf("Failed to unmarshal policy admission request err %v", err),
|
||||
}}
|
||||
}
|
||||
// check for uniqueness of rule names while CREATE/DELET
|
||||
admissionResp = ws.validateUniqueRuleName(policy)
|
||||
|
||||
if err := policy.Validate(); err != nil {
|
||||
admissionResp = &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to evaluate if policy has validtion or mutation rules defined
|
||||
hasMutateOrValidate := func() bool {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) || !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
|
||||
if rule.HasMutate() || rule.HasValidate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -52,64 +57,11 @@ func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionReques
|
|||
return admissionResp
|
||||
}
|
||||
|
||||
func (ws *WebhookServer) validatePolicy(policy *kyverno.ClusterPolicy) *v1beta1.AdmissionResponse {
|
||||
admissionResp := ws.validateUniqueRuleName(policy)
|
||||
if !admissionResp.Allowed {
|
||||
return admissionResp
|
||||
}
|
||||
|
||||
return ws.validateOverlayPattern(policy)
|
||||
}
|
||||
|
||||
func (ws *WebhookServer) validateOverlayPattern(policy *kyverno.ClusterPolicy) *v1beta1.AdmissionResponse {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.Validation.Pattern == nil && len(rule.Validation.AnyPattern) == 0 {
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: fmt.Sprintf("Invalid policy, neither pattern nor anyPattern found in validate rule %s", rule.Name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Validation.Pattern != nil && len(rule.Validation.AnyPattern) != 0 {
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: fmt.Sprintf("Invalid policy, either pattern or anyPattern is allowed in validate rule %s", rule.Name),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &v1beta1.AdmissionResponse{Allowed: true}
|
||||
}
|
||||
|
||||
// Verify if the Rule names are unique within a policy
|
||||
func (ws *WebhookServer) validateUniqueRuleName(policy *kyverno.ClusterPolicy) *v1beta1.AdmissionResponse {
|
||||
var ruleNames []string
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if utils.ContainsString(ruleNames, rule.Name) {
|
||||
msg := fmt.Sprintf(`The policy "%s" is invalid: duplicate rule name: "%s"`, policy.Name, rule.Name)
|
||||
glog.Errorln(msg)
|
||||
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
ruleNames = append(ruleNames, rule.Name)
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Policy validation passed")
|
||||
func failResponseWithMsg(msg string) *v1beta1.AdmissionResponse {
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,10 @@ expected:
|
|||
rules:
|
||||
- name: check-default-namespace
|
||||
type: Validation
|
||||
message: "Validation rule 'check-default-namespace' failed to validate patterns defined in anyPattern. A none 'default' namespace is required; anyPattern[0] failed at path /metadata/namespace/"
|
||||
message: "Validation rule 'check-default-namespace' failed at '/metadata/namespace/' for resource Pod/default/myapp-pod. A none 'default' namespace is required"
|
||||
success: false
|
||||
- name: check-namespace-exist
|
||||
type: Validation
|
||||
message: "Validation rule 'check-namespace-exist' anyPattern[0] succesfully validated"
|
||||
message: "Validation rule 'check-namespace-exist' succesfully validated"
|
||||
success: true
|
||||
|
||||
|
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: validate-hostpid-hostipc
|
||||
type: Validation
|
||||
message: Validation rule 'validate-hostpid-hostipc' failed to validate patterns defined in anyPattern. Disallow use of host's pid namespace and host's ipc namespace; anyPattern[0] failed at path /spec/hostIPC/
|
||||
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
|
|
@ -14,5 +14,5 @@ expected:
|
|||
rules:
|
||||
- name: validate-not-readonly-rootfilesystem
|
||||
type: Validation
|
||||
message: Validation rule 'validate-not-readonly-rootfilesystem' failed to validate patterns defined in anyPattern. Container should not have read-only rootfilesystem; anyPattern[0] failed at path /spec/container/
|
||||
message: Validation rule 'validate-not-readonly-rootfilesystem' failed at '/spec/containers/0/securityContext/readOnlyRootFilesystem/' for resource Pod//ghost-with-readonly-rootfilesystem. Container should not have read-only rootfilesystem
|
||||
success: false
|
Loading…
Add table
Reference in a new issue