1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 18:38:40 +00:00

feat: use kind selectors ()

* fix: compile regex globally

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* feat: use kind selectors

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* clean

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* cache

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* kuttl

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* kuttl

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* webhooks rules

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* kuttl

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-03-10 14:24:55 +01:00 committed by GitHub
parent a61dac613b
commit 5160b63154
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 776 additions and 26 deletions

View file

@ -8,14 +8,17 @@ import (
openapiv2 "github.com/google/gnostic/openapiv2"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/kyverno/kyverno/pkg/utils/wildcard"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
)
// IDiscovery provides interface to mange Kind and GVR mapping
type IDiscovery interface {
FindResources(group, version, kind, subresource string) ([]schema.GroupVersionResource, error)
FindResource(groupVersion string, kind string) (apiResource, parentAPIResource *metav1.APIResource, gvr schema.GroupVersionResource, err error)
// TODO: there's no mapping from GVK to GVR, this is very error prone
GetGVRFromGVK(schema.GroupVersionKind) (schema.GroupVersionResource, error)
@ -145,6 +148,97 @@ func (c serverResources) FindResource(groupVersion string, kind string) (apiReso
return nil, nil, schema.GroupVersionResource{}, err
}
func (c serverResources) FindResources(group, version, kind, subresource string) ([]schema.GroupVersionResource, error) {
resources, err := c.findResources(group, version, kind, subresource)
if err != nil {
if !c.cachedClient.Fresh() {
c.cachedClient.Invalidate()
return c.findResources(group, version, kind, subresource)
}
}
return resources, err
}
func (c serverResources) findResources(group, version, kind, subresource string) ([]schema.GroupVersionResource, error) {
_, serverGroupsAndResources, err := c.cachedClient.ServerGroupsAndResources()
if err != nil && !strings.Contains(err.Error(), "Got empty response for") {
if discovery.IsGroupDiscoveryFailedError(err) {
logDiscoveryErrors(err)
} else if isServerCurrentlyUnableToHandleRequest(err) {
logger.Error(err, "failed to find preferred resource version")
} else {
logger.Error(err, "failed to find preferred resource version")
return nil, err
}
}
getGVK := func(gv schema.GroupVersion, group, version, kind string) schema.GroupVersionKind {
if group == "" {
group = gv.Group
}
if version == "" {
version = gv.Version
}
return schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
}
}
resources := sets.New[schema.GroupVersionResource]()
// first match resouces
for _, list := range serverGroupsAndResources {
gv, err := schema.ParseGroupVersion(list.GroupVersion)
if err != nil {
return nil, err
} else {
for _, resource := range list.APIResources {
if !strings.Contains(resource.Name, "/") {
gvk := getGVK(gv, resource.Group, resource.Version, resource.Kind)
if wildcard.Match(group, gvk.Group) && wildcard.Match(version, gvk.Version) && wildcard.Match(kind, gvk.Kind) {
resources.Insert(gvk.GroupVersion().WithResource(resource.Name))
}
}
}
}
}
// second match subresouces if necessary
subresources := sets.New[schema.GroupVersionResource]()
if subresource != "" {
for _, list := range serverGroupsAndResources {
for _, resource := range list.APIResources {
for parent := range resources {
if wildcard.Match(parent.Resource+"/"+subresource, resource.Name) {
subresources.Insert(parent.GroupVersion().WithResource(resource.Name))
break
}
}
}
}
}
// third if no resource matched, try again but consider subresources this time
if resources.Len() == 0 {
for _, list := range serverGroupsAndResources {
gv, err := schema.ParseGroupVersion(list.GroupVersion)
if err != nil {
return nil, err
} else {
for _, resource := range list.APIResources {
gvk := getGVK(gv, resource.Group, resource.Version, resource.Kind)
if wildcard.Match(group, gvk.Group) && wildcard.Match(version, gvk.Version) && wildcard.Match(kind, gvk.Kind) {
resources.Insert(gv.WithResource(resource.Name))
}
}
}
}
}
if kind == "*" && subresource == "*" {
return resources.Union(subresources).UnsortedList(), nil
} else if subresource != "" {
return subresources.UnsortedList(), nil
}
return resources.UnsortedList(), nil
}
func (c serverResources) findResource(groupVersion string, kind string) (apiResource, parentAPIResource *metav1.APIResource,
gvr schema.GroupVersionResource, err error,
) {

View file

@ -86,6 +86,10 @@ func (c *fakeDiscoveryClient) FindResource(groupVersion string, kind string) (ap
return nil, nil, schema.GroupVersionResource{}, fmt.Errorf("not implemented")
}
func (c *fakeDiscoveryClient) FindResources(group, version, kind, subresource string) ([]schema.GroupVersionResource, error) {
return nil, fmt.Errorf("not implemented")
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapiv2.Document, error) {
return nil, nil
}

View file

@ -20,9 +20,16 @@ func logDiscoveryErrors(err error) {
}
}
// isServerCurrentlyUnableToHandleRequest returns true if the error is related to the discovery not able to handle the request
// this can happen with aggregated services when the api server can't get a `TokenReview` and is not able to send requests to
// the underlying service, this is typically due to kyverno blocking `TokenReview` admission requests.
func isServerCurrentlyUnableToHandleRequest(err error) bool {
return err != nil && strings.Contains(err.Error(), "the server is currently unable to handle the request")
}
func isMetricsServerUnavailable(gv schema.GroupVersion, err error) bool {
// error message is defined at:
// https://github.com/kubernetes/apimachinery/blob/2456ebdaba229616fab2161a615148884b46644b/pkg/api/errors/errors.go#L432
return (gv.Group == "metrics.k8s.io" || gv.Group == "custom.metrics.k8s.io" || gv.Group == "external.metrics.k8s.io") &&
strings.Contains(err.Error(), "the server is currently unable to handle the request")
isServerCurrentlyUnableToHandleRequest(err)
}

View file

@ -0,0 +1,105 @@
package dclient
import (
"errors"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func Test_isServerCurrentlyUnableToHandleRequest(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{{
args: args{
err: nil,
},
want: false,
}, {
args: args{
err: errors.New("another error"),
},
want: false,
}, {
args: args{
err: errors.New("the server is currently unable to handle the request"),
},
want: true,
}, {
args: args{
err: errors.New("a prefix : the server is currently unable to handle the request"),
},
want: true,
}, {
args: args{
err: errors.New("the server is currently unable to handle the request - a suffix"),
},
want: true,
}, {
args: args{
err: errors.New("a prefix : the server is currently unable to handle the request - a suffix"),
},
want: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isServerCurrentlyUnableToHandleRequest(tt.args.err); got != tt.want {
t.Errorf("isServerCurrentlyUnableToHandleRequest() = %v, want %v", got, tt.want)
}
})
}
}
func Test_isMetricsServerUnavailable(t *testing.T) {
type args struct {
gv schema.GroupVersion
err error
}
tests := []struct {
name string
args args
want bool
}{{
args: args{
gv: schema.GroupVersion{Group: "core", Version: "v1"},
err: nil,
},
want: false,
}, {
args: args{
gv: schema.GroupVersion{Group: "core", Version: "v1"},
err: errors.New("the server is currently unable to handle the request"),
},
want: false,
}, {
args: args{
gv: schema.GroupVersion{Group: "metrics.k8s.io", Version: "v1"},
err: errors.New("the server is currently unable to handle the request"),
},
want: true,
}, {
args: args{
gv: schema.GroupVersion{Group: "custom.metrics.k8s.io", Version: "v1"},
err: errors.New("the server is currently unable to handle the request"),
},
want: true,
}, {
args: args{
gv: schema.GroupVersion{Group: "external.metrics.k8s.io", Version: "v1"},
err: errors.New("the server is currently unable to handle the request"),
},
want: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isMetricsServerUnavailable(tt.args.gv, tt.args.err); got != tt.want {
t.Errorf("isMetricsServerUnavailable() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -847,22 +847,13 @@ func (c *controller) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface
if _, ok := gvkMap[gvk]; !ok {
gvkMap[gvk] = 1
// NOTE: webhook stores GVR in its rules while policy stores GVK in its rules definition
gv, k := kubeutils.GetKindFromGVK(gvk)
_, parentAPIResource, gvr, err := c.discoveryClient.FindResource(gv, k)
group, version, kind, subresource := kubeutils.ParseKindSelector(gvk)
gvrs, err := c.discoveryClient.FindResources(group, version, kind, subresource)
if err != nil {
logger.Error(err, "unable to convert GVK to GVR", "GVK", gvk)
logger.Error(err, "unable to find resource", "group", group, "version", version, "kind", kind, "subresource", subresource)
continue
}
if parentAPIResource != nil {
gvr = schema.GroupVersionResource{
Group: parentAPIResource.Group,
Version: parentAPIResource.Version,
Resource: gvr.Resource,
}
}
if strings.Contains(gvk, "*") {
gvrList = append(gvrList, schema.GroupVersionResource{Group: gvr.Group, Version: "*", Resource: gvr.Resource})
} else {
for _, gvr := range gvrs {
logger.V(4).Info("configuring webhook", "GVK", gvk, "GVR", gvr)
gvrList = append(gvrList, gvr)
}

View file

@ -17,30 +17,28 @@ import (
type webhook struct {
maxWebhookTimeout int32
failurePolicy admissionregistrationv1.FailurePolicyType
rules map[schema.GroupVersionResource]struct{}
rules map[schema.GroupVersion]sets.Set[string]
}
func newWebhook(timeout int32, failurePolicy admissionregistrationv1.FailurePolicyType) *webhook {
return &webhook{
maxWebhookTimeout: timeout,
failurePolicy: failurePolicy,
rules: map[schema.GroupVersionResource]struct{}{},
rules: map[schema.GroupVersion]sets.Set[string]{},
}
}
func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.OperationType) []admissionregistrationv1.RuleWithOperations {
var rules []admissionregistrationv1.RuleWithOperations
for gvr := range wh.rules {
resources := sets.New(gvr.Resource)
ephemeralContainersGVR := schema.GroupVersionResource{Resource: "pods/ephemeralcontainers", Group: "", Version: "v1"}
_, rulesContainEphemeralContainers := wh.rules[ephemeralContainersGVR]
if resources.Has("pods") && !rulesContainEphemeralContainers {
for gv, resources := range wh.rules {
// if we have pods, we add pods/ephemeralcontainers by default
if gv.Group == "" && gv.Version == "v1" && resources.Has("pods") {
resources.Insert("pods/ephemeralcontainers")
}
rules = append(rules, admissionregistrationv1.RuleWithOperations{
Rule: admissionregistrationv1.Rule{
APIGroups: []string{gvr.Group},
APIVersions: []string{gvr.Version},
APIGroups: []string{gv.Group},
APIVersions: []string{gv.Version},
Resources: sets.List(resources),
},
Operations: ops,
@ -73,7 +71,13 @@ func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.Opera
}
func (wh *webhook) set(gvr schema.GroupVersionResource) {
wh.rules[gvr] = struct{}{}
gv := gvr.GroupVersion()
resources := wh.rules[gv]
if resources == nil {
wh.rules[gv] = sets.New(gvr.Resource)
} else {
resources.Insert(gvr.Resource)
}
}
func (wh *webhook) isEmpty() bool {
@ -81,8 +85,8 @@ func (wh *webhook) isEmpty() bool {
}
func (wh *webhook) setWildcard() {
wh.rules = map[schema.GroupVersionResource]struct{}{
{Group: "*", Version: "*", Resource: "*/*"}: {},
wh.rules = map[schema.GroupVersion]sets.Set[string]{
{Group: "*", Version: "*"}: sets.New("*/*"),
}
}

View file

@ -9,6 +9,45 @@ import (
var versionRegex = regexp.MustCompile(`^v\d((alpha|beta)\d)?|\*$`)
func ParseKindSelector(input string) (string, string, string, string) {
parts := strings.Split(input, "/")
if len(parts) > 0 {
parts = append(parts[:len(parts)-1], strings.Split(parts[len(parts)-1], ".")...)
}
switch len(parts) {
case 1:
// we have only kind
return "*", "*", parts[0], ""
case 2:
// `*/*` means all resources and subresources
if parts[0] == "*" && parts[1] == "*" {
return "*", "*", "*", "*"
}
// detect the `*/subresource` case when part[1] is all lowercase
if parts[0] == "*" && strings.ToLower(parts[1]) == parts[1] {
return "*", "*", parts[0], parts[1]
}
// if the first part is `*` or a version we have version/kind
if versionRegex.MatchString(parts[0]) {
return "*", parts[0], parts[1], ""
}
// we have kind/subresource
return "*", "*", parts[0], parts[1]
case 3:
// if the first part is `*` or a version we have version/kind/subresource
if versionRegex.MatchString(parts[0]) {
return "*", parts[0], parts[1], parts[2]
}
// we have group/version/kind
return parts[0], parts[1], parts[2], ""
case 4:
// we have group/version/kind/subresource
return parts[0], parts[1], parts[2], parts[3]
default:
return "", "", "", ""
}
}
// GetKindFromGVK - get kind and APIVersion from GVK
func GetKindFromGVK(str string) (string, string) {
parts := strings.Split(str, "/")

View file

@ -108,3 +108,106 @@ func Test_GroupVersionMatches(t *testing.T) {
groupVersion, serverResourceGroupVersion = "certificates.k8s.io/*", "networking.k8s.io/v1"
assert.Equal(t, GroupVersionMatches(groupVersion, serverResourceGroupVersion), false)
}
func TestParseKindSelector(t *testing.T) {
type args struct {
input string
}
type want struct {
group string
version string
kind string
subresource string
}
tests := []struct {
name string
args args
want want
}{{
args: args{"*"},
want: want{"*", "*", "*", ""},
}, {
args: args{"*.*"},
want: want{"*", "*", "*", "*"},
}, {
args: args{"*/*"},
want: want{"*", "*", "*", "*"},
}, {
args: args{"Pod"},
want: want{"*", "*", "Pod", ""},
}, {
args: args{"v1/Pod"},
want: want{"*", "v1", "Pod", ""},
}, {
args: args{"batch/*/CronJob"},
want: want{"batch", "*", "CronJob", ""},
}, {
args: args{"storage.k8s.io/v1/CSIDriver"},
want: want{"storage.k8s.io", "v1", "CSIDriver", ""},
}, {
args: args{"tekton.dev/v1beta1/TaskRun/status"},
want: want{"tekton.dev", "v1beta1", "TaskRun", "status"},
}, {
args: args{"v1/Pod.status"},
want: want{"*", "v1", "Pod", "status"},
}, {
args: args{"v1/Pod/status"},
want: want{"*", "v1", "Pod", "status"},
}, {
args: args{"Pod.status"},
want: want{"*", "*", "Pod", "status"},
}, {
args: args{"Pod/status"},
want: want{"*", "*", "Pod", "status"},
}, {
args: args{"apps/v1/Deployment/scale"},
want: want{"apps", "v1", "Deployment", "scale"},
}, {
args: args{"v1/ReplicationController/scale"},
want: want{"*", "v1", "ReplicationController", "scale"},
}, {
args: args{"*/ReplicationController/scale"},
want: want{"*", "*", "ReplicationController", "scale"},
}, {
args: args{"*/Deployment/scale"},
want: want{"*", "*", "Deployment", "scale"},
}, {
args: args{"*/Deployment.scale"},
want: want{"*", "*", "Deployment", "scale"},
}, {
args: args{"apps/v1/Deployment.scale"},
want: want{"apps", "v1", "Deployment", "scale"},
}, {
args: args{"*/scale"},
want: want{"*", "*", "*", "scale"},
}, {
args: args{"Pod/*"},
want: want{"*", "*", "Pod", "*"},
}, {
args: args{"*/*/*"},
want: want{"*", "*", "*", "*"},
}, {
args: args{"*/*/*/*"},
want: want{"*", "*", "*", "*"},
}, {
args: args{"*/*/*/*/*"},
want: want{"", "", "", ""},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
group, version, kind, subresource := ParseKindSelector(tt.args.input)
if group != tt.want.group {
t.Errorf("ParseKindSelector() group = %v, want %v", group, tt.want.group)
}
if version != tt.want.version {
t.Errorf("ParseKindSelector() version = %v, want %v", version, tt.want.version)
}
if kind != tt.want.kind {
t.Errorf("ParseKindSelector() kind = %v, want %v", kind, tt.want.kind)
}
if subresource != tt.want.subresource {
t.Errorf("ParseKindSelector() subresource = %v, want %v", subresource, tt.want.subresource)
}
})
}
}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,9 @@
## Description
This test verifies the resource validation webhook is configured correctly when a policy targets all `*/scale` subresources.
## Steps
1. - Create a policy targeting `*/scale`
- Assert policy gets ready
1. - Assert that the resource validation webhook is configured correctly

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
pod-policies.kyverno.io/autogen-controllers: none
spec:
validationFailureAction: Audit
background: false
rules:
- name: require-team
match:
any:
- resources:
kinds:
- '*/scale'
validate:
message: 'The label `team` is required.'
pattern:
metadata:
labels:
team: '?*'

View file

@ -0,0 +1,34 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg
webhooks:
- rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- replicationcontrollers/scale
scope: '*'
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- deployments/scale
- replicasets/scale
- statefulsets/scale
scope: '*'

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- webhooks.yaml

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- webhooks.yaml

View file

@ -0,0 +1,9 @@
## Description
This test verifies the resource validation webhook is configured correctly when a policy targets `Pod`.
## Steps
1. - Create a policy targeting `Pod`
- Assert policy gets ready
1. - Assert that the resource validation webhook is configured correctly

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
pod-policies.kyverno.io/autogen-controllers: none
spec:
validationFailureAction: Audit
background: false
rules:
- name: require-team
match:
any:
- resources:
kinds:
- Pod
validate:
message: 'The label `team` is required.'
pattern:
metadata:
labels:
team: '?*'

View file

@ -0,0 +1,21 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg
webhooks:
- rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- pods
- pods/ephemeralcontainers
scope: '*'

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- webhooks.yaml

View file

@ -0,0 +1,9 @@
## Description
This test verifies the resource validation webhook is configured correctly when a policy targets all `Pod/*` subresources.
## Steps
1. - Create a policy targeting `Pod/*`
- Assert policy gets ready
1. - Assert that the resource validation webhook is configured correctly

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
pod-policies.kyverno.io/autogen-controllers: none
spec:
validationFailureAction: Audit
background: false
rules:
- name: require-team
match:
any:
- resources:
kinds:
- Pod/*
validate:
message: 'The label `team` is required.'
pattern:
metadata:
labels:
team: '?*'

View file

@ -0,0 +1,28 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg
webhooks:
- rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- pods/attach
- pods/binding
- pods/ephemeralcontainers
- pods/eviction
- pods/exec
- pods/log
- pods/portforward
- pods/proxy
- pods/status
scope: '*'

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- webhooks.yaml

View file

@ -0,0 +1,10 @@
## Description
This test verifies the resource validation webhook is configured correctly when a policy targets all `Scale` resource.
It should be equivalent to using `*/scale`
## Steps
1. - Create a policy targeting `Scale`
- Assert policy gets ready
1. - Assert that the resource validation webhook is configured correctly

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
pod-policies.kyverno.io/autogen-controllers: none
spec:
validationFailureAction: Audit
background: false
rules:
- name: require-team
match:
any:
- resources:
kinds:
- Scale
validate:
message: 'The label `team` is required.'
pattern:
metadata:
labels:
team: '?*'

View file

@ -0,0 +1,34 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg
webhooks:
- rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- replicationcontrollers/scale
scope: '*'
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- deployments/scale
- replicasets/scale
- statefulsets/scale
scope: '*'

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- webhooks.yaml

View file

@ -0,0 +1,9 @@
## Description
This test verifies the resource validation webhook is configured correctly when a policy targets all `*` resource.
## Steps
1. - Create a policy targeting `*`
- Assert policy gets ready
1. - Assert that the resource validation webhook is configured correctly

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
pod-policies.kyverno.io/autogen-controllers: none
spec:
validationFailureAction: Audit
background: false
rules:
- name: require-team
match:
any:
- resources:
kinds:
- '*'
validate:
message: 'The label `team` is required.'
pattern:
metadata:
labels:
team: '?*'

View file

@ -0,0 +1,35 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg
webhooks:
- failurePolicy: Ignore
rules:
- apiGroups:
- '*'
apiVersions:
- '*'
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- '*/*'
scope: '*'
- failurePolicy: Fail
rules:
- apiGroups:
- '*'
apiVersions:
- '*'
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- '*/*'
scope: '*'