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

adding support for multiple names in match and exclude blocks (#2010)

* add names in rd struct

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* added checking logic

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* updated yamls

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* wip: fix empty set problem

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* working with exclude

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* fixing name and names

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* added error if both name and names are specified

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* added tests

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* changed empty set logic, fixed whitespaces and comments

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>

* fix match and exclude bug

Signed-off-by: RinkiyaKeDad <arshsharma461@gmail.com>
This commit is contained in:
Arsh Sharma 2021-06-29 11:01:22 +05:30 committed by GitHub
parent 8556cf6c91
commit fbc80cdfae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 3675 additions and 978 deletions

File diff suppressed because it is too large Load diff

View file

@ -146,6 +146,13 @@ spec:
supports wildcard characters "*" (matches zero or
many characters) and "?" (at least one character).
type: string
names:
description: Names are the names of the resources. Each
name supports wildcard characters "*" (matches zero
or many characters) and "?" (at least one character).
items:
type: string
type: array
namespaceSelector:
description: 'NamespaceSelector is a label selector
for the resource namespace. Label keys and values
@ -382,6 +389,13 @@ spec:
supports wildcard characters "*" (matches zero or
many characters) and "?" (at least one character).
type: string
names:
description: Names are the names of the resources. Each
name supports wildcard characters "*" (matches zero
or many characters) and "?" (at least one character).
items:
type: string
type: array
namespaceSelector:
description: 'NamespaceSelector is a label selector
for the resource namespace. Label keys and values

View file

@ -147,6 +147,13 @@ spec:
supports wildcard characters "*" (matches zero or
many characters) and "?" (at least one character).
type: string
names:
description: Names are the names of the resources. Each
name supports wildcard characters "*" (matches zero
or many characters) and "?" (at least one character).
items:
type: string
type: array
namespaceSelector:
description: 'NamespaceSelector is a label selector
for the resource namespace. Label keys and values
@ -383,6 +390,13 @@ spec:
supports wildcard characters "*" (matches zero or
many characters) and "?" (at least one character).
type: string
names:
description: Names are the names of the resources. Each
name supports wildcard characters "*" (matches zero
or many characters) and "?" (at least one character).
items:
type: string
type: array
namespaceSelector:
description: 'NamespaceSelector is a label selector
for the resource namespace. Label keys and values

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -263,6 +263,11 @@ type ResourceDescription struct {
// +optional
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Names are the names of the resources. Each name supports wildcard characters
// "*" (matches zero or many characters) and "?" (at least one character).
// +optional
Names []string `json:"names,omitempty" yaml:"names,omitempty"`
// Namespaces is a list of namespaces names. Each name supports wildcard characters
// "*" (matches zero or many characters) and "?" (at least one character).
// +optional

View file

@ -135,6 +135,19 @@ func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription,
}
}
if len(conditionBlock.Names) > 0 {
noneMatch := true
for i := range conditionBlock.Names {
if checkName(conditionBlock.Names[i], resource.GetName()) {
noneMatch = false
break
}
}
if noneMatch {
errs = append(errs, fmt.Errorf("none of the names match"))
}
}
if len(conditionBlock.Namespaces) > 0 {
if !checkNameSpace(conditionBlock.Namespaces, resource) {
errs = append(errs, fmt.Errorf("namespace does not match"))

View file

@ -41,6 +41,30 @@ func TestMatchesResourceDescription(t *testing.T) {
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: false,
},
{
Description: "Should fail since resource does not match because of names field",
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"],"names": ["dev-*"]},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: true,
},
{
Description: "Should pass since resource matches a name in the names field",
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"],"names": ["dev-*","hello-world"]},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: false,
},
{
Description: "Should fail since resource gets excluded because of the names field",
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"names": ["dev-*","hello-*"]}},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: true,
},
{
Description: "Should pass since resource does not get excluded because of the names field",
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"bye-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"names": ["dev-*","hello-*"]}},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
areErrorsExpected: false,
},
{
Description: "Should fail since resource does not match policy",
AdmissionInfo: kyverno.RequestInfo{

View file

@ -177,6 +177,7 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
// doMatchAndExcludeConflict checks if the resultant
// of match and exclude block is not an empty set
// returns true if it is an empty set
func doMatchAndExcludeConflict(rule kyverno.Rule) bool {
if reflect.DeepEqual(rule.ExcludeResources, kyverno.ExcludeResources{}) {
@ -268,6 +269,28 @@ func doMatchAndExcludeConflict(rule kyverno.Rule) bool {
}
}
if len(rule.ExcludeResources.ResourceDescription.Names) > 0 {
excludeSlice := rule.ExcludeResources.ResourceDescription.Names
matchSlice := rule.MatchResources.ResourceDescription.Names
// if exclude block has something and match doesn't it means we
// have a non empty set
if len(rule.MatchResources.ResourceDescription.Names) == 0 {
return false
}
// if *any* name in match and exclude conflicts
// we want user to fix that
for _, matchName := range matchSlice {
for _, excludeName := range excludeSlice {
if wildcard.Match(excludeName, matchName) {
return true
}
}
}
return false
}
if len(excludeNamespaces) > 0 {
if len(rule.MatchResources.ResourceDescription.Namespaces) == 0 {
return false
@ -715,6 +738,10 @@ func validateMatchedResourceDescription(rd kyverno.ResourceDescription) (string,
return "", fmt.Errorf("match resources not specified")
}
if rd.Name != "" && len(rd.Names) > 0 {
return "", fmt.Errorf("both name and names can not be specified together")
}
if err := validateResourceDescription(rd); err != nil {
return "match", err
}
@ -778,6 +805,11 @@ func validateExcludeResourceDescription(rd kyverno.ResourceDescription) (string,
// exclude is not mandatory
return "", nil
}
if rd.Name != "" && len(rd.Names) > 0 {
return "", fmt.Errorf("both name and names can not be specified together")
}
if err := validateResourceDescription(rd); err != nil {
return "exclude", err
}