1
0
Fork 0
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:
Shivkumar Dudhani 2019-10-01 16:25:09 -07:00 committed by GitHub
commit e02d334dfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1067 additions and 154 deletions

View file

@ -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: "?*"

View file

@ -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

View file

@ -15,8 +15,8 @@ spec:
- Pod
validate:
message: "Container should not have read-only rootfilesystem"
anyPattern:
- spec:
container:
pattern:
spec:
containers:
- securityContext:
readOnlyRootFilesystem: false

View file

@ -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

View file

@ -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)]
}

View 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
}

View 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)
}

View file

@ -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

View file

@ -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))
}

View file

@ -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,
},
}
}

View file

@ -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

View file

@ -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

View file

@ -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