mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
fix: Add subresources support to policy exceptions (#5839)
Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com> Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
40836ff6fc
commit
9d2deb0568
14 changed files with 275 additions and 136 deletions
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/config"
|
||||
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
|
||||
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
||||
match "github.com/kyverno/kyverno/pkg/utils/match"
|
||||
"github.com/kyverno/kyverno/pkg/utils/match"
|
||||
"go.uber.org/multierr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
|
@ -94,13 +94,13 @@ func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy
|
|||
debug.Info("resource namespace didn't match policy namespace", "result", err)
|
||||
}
|
||||
// match resource with match/exclude clause
|
||||
matched := match.CheckMatchesResources(resource, spec.MatchResources, nsLabels)
|
||||
matched := match.CheckMatchesResources(resource, spec.MatchResources, nsLabels, nil, "")
|
||||
if matched != nil {
|
||||
debug.Info("resource/match didn't match", "result", matched)
|
||||
continue
|
||||
}
|
||||
if spec.ExcludeResources != nil {
|
||||
excluded := match.CheckMatchesResources(resource, *spec.ExcludeResources, nsLabels)
|
||||
excluded := match.CheckMatchesResources(resource, *spec.ExcludeResources, nsLabels, nil, "")
|
||||
if excluded == nil {
|
||||
debug.Info("resource/exclude matched")
|
||||
continue
|
||||
|
|
|
@ -70,8 +70,12 @@ func filterRule(rclient registryclient.Client, rule kyvernov1.Rule, policyContex
|
|||
}
|
||||
|
||||
logger := logging.WithName("exception")
|
||||
|
||||
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
|
||||
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext)
|
||||
|
||||
// check if there is a corresponding policy exception
|
||||
ruleResp := hasPolicyExceptions(policyContext, &rule, logger)
|
||||
ruleResp := hasPolicyExceptions(policyContext, &rule, subresourceGVKToAPIResource, logger)
|
||||
if ruleResp != nil {
|
||||
return ruleResp
|
||||
}
|
||||
|
@ -94,9 +98,6 @@ func filterRule(rclient registryclient.Client, rule kyvernov1.Rule, policyContex
|
|||
logger = logging.WithName(string(ruleType)).WithValues("policy", policy.GetName(),
|
||||
"kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
|
||||
|
||||
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
|
||||
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext)
|
||||
|
||||
if err := MatchesResourceDescription(subresourceGVKToAPIResource, newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, "", policyContext.subresource); err != nil {
|
||||
if ruleType == response.Generation {
|
||||
// if the oldResource matched, return "false" to delete GR for it
|
||||
|
|
|
@ -106,12 +106,15 @@ func VerifyAndPatchImages(
|
|||
return
|
||||
}
|
||||
|
||||
if !matches(logger, rule, policyContext) {
|
||||
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
|
||||
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, policyContext)
|
||||
|
||||
if !matches(logger, rule, policyContext, subresourceGVKToAPIResource) {
|
||||
return
|
||||
}
|
||||
|
||||
// check if there is a corresponding policy exception
|
||||
ruleResp := hasPolicyExceptions(policyContext, rule, logger)
|
||||
ruleResp := hasPolicyExceptions(policyContext, rule, subresourceGVKToAPIResource, logger)
|
||||
if ruleResp != nil {
|
||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
|
||||
return
|
||||
|
|
|
@ -73,7 +73,7 @@ func Mutate(ctx context.Context, rclient registryclient.Client, policyContext *P
|
|||
}
|
||||
|
||||
// check if there is a corresponding policy exception
|
||||
ruleResp := hasPolicyExceptions(policyContext, &computeRules[i], logger)
|
||||
ruleResp := hasPolicyExceptions(policyContext, &computeRules[i], subresourceGVKToAPIResource, logger)
|
||||
if ruleResp != nil {
|
||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
|
||||
return
|
||||
|
|
|
@ -17,19 +17,16 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/wildcards"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
matched "github.com/kyverno/kyverno/pkg/utils/match"
|
||||
"github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// EngineStats stores in the statistics for a single application of resource
|
||||
|
@ -40,32 +37,6 @@ type EngineStats struct {
|
|||
RulesAppliedCount int
|
||||
}
|
||||
|
||||
func checkKind(subresourceGVKToAPIResource map[string]*metav1.APIResource, kinds []string, gvk schema.GroupVersionKind, subresourceInAdmnReview string) bool {
|
||||
title := cases.Title(language.Und, cases.NoLower)
|
||||
result := false
|
||||
for _, k := range kinds {
|
||||
if k != "*" {
|
||||
gv, kind := kubeutils.GetKindFromGVK(k)
|
||||
apiResource, ok := subresourceGVKToAPIResource[k]
|
||||
if ok {
|
||||
result = apiResource.Group == gvk.Group && (apiResource.Version == gvk.Version || strings.Contains(gv, "*")) && apiResource.Kind == gvk.Kind
|
||||
} else { // if the kind is not found in the subresourceGVKToAPIResource, then it is not a subresource
|
||||
result = title.String(kind) == gvk.Kind && subresourceInAdmnReview == ""
|
||||
if gv != "" {
|
||||
result = result && kubeutils.GroupVersionMatches(gv, gvk.GroupVersion().String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = true
|
||||
}
|
||||
|
||||
if result {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func checkName(name, resourceName string) bool {
|
||||
return wildcard.Match(name, resourceName)
|
||||
}
|
||||
|
@ -145,7 +116,7 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met
|
|||
var errs []error
|
||||
|
||||
if len(conditionBlock.Kinds) > 0 {
|
||||
if !checkKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview) {
|
||||
if !matched.CheckKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview) {
|
||||
errs = append(errs, fmt.Errorf("kind does not match %v", conditionBlock.Kinds))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
||||
"github.com/kyverno/kyverno/pkg/autogen"
|
||||
|
@ -2469,68 +2467,3 @@ func TestManagedPodResource(t *testing.T) {
|
|||
assert.Equal(t, res, tc.expectedResult, "test %d/%s failed, expect %v, got %v", i+1, tc.name, tc.expectedResult, res)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkKind(t *testing.T) {
|
||||
subresourceGVKToAPIResource := make(map[string]*metav1.APIResource)
|
||||
match := checkKind(subresourceGVKToAPIResource, []string{"*"}, schema.GroupVersionKind{Kind: "Deployment", Group: "", Version: "v1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"tekton.dev/v1beta1/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1beta1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"tekton.dev/*/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1alpha1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
// Though both 'pods', 'pods/status' have same kind i.e. 'Pod' but they are different resources, 'subresourceInAdmnReview' is used in determining that.
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status")
|
||||
assert.Equal(t, match, false)
|
||||
|
||||
// Though both 'pods', 'pods/status' have same kind i.e. 'Pod' but they are different resources, 'subresourceInAdmnReview' is used in determining that.
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers")
|
||||
assert.Equal(t, match, false)
|
||||
|
||||
subresourceGVKToAPIResource["networking.k8s.io/v1/NetworkPolicy/status"] = &metav1.APIResource{
|
||||
Name: "networkpolicies/status",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Kind: "NetworkPolicy",
|
||||
Group: "networking.k8s.io",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
subresourceGVKToAPIResource["v1/Pod.status"] = &metav1.APIResource{
|
||||
Name: "pods/status",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Kind: "Pod",
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
subresourceGVKToAPIResource["*/Pod.eviction"] = &metav1.APIResource{
|
||||
Name: "pods/eviction",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Kind: "Eviction",
|
||||
Group: "policy",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"networking.k8s.io/v1/NetworkPolicy/status"}, schema.GroupVersionKind{Kind: "NetworkPolicy", Group: "networking.k8s.io", Version: "v1"}, "status")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"v1/Pod.status"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"*/Pod.eviction"}, schema.GroupVersionKind{Kind: "Eviction", Group: "policy", Version: "v1"}, "eviction")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = checkKind(subresourceGVKToAPIResource, []string{"v1alpha1/Pod.eviction"}, schema.GroupVersionKind{Kind: "Eviction", Group: "policy", Version: "v1"}, "eviction")
|
||||
assert.Equal(t, match, false)
|
||||
}
|
||||
|
|
|
@ -133,11 +133,14 @@ func validateResource(ctx context.Context, log logr.Logger, rclient registryclie
|
|||
return nil
|
||||
}
|
||||
log = log.WithValues("rule", rule.Name)
|
||||
if !matches(log, rule, enginectx) {
|
||||
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
|
||||
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, enginectx)
|
||||
|
||||
if !matches(log, rule, enginectx, subresourceGVKToAPIResource) {
|
||||
return nil
|
||||
}
|
||||
// check if there is a corresponding policy exception
|
||||
ruleResp := hasPolicyExceptions(enginectx, rule, log)
|
||||
ruleResp := hasPolicyExceptions(enginectx, rule, subresourceGVKToAPIResource, log)
|
||||
if ruleResp != nil {
|
||||
return ruleResp
|
||||
}
|
||||
|
@ -573,10 +576,7 @@ func isEmptyUnstructured(u *unstructured.Unstructured) bool {
|
|||
}
|
||||
|
||||
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
|
||||
func matches(logger logr.Logger, rule *kyvernov1.Rule, ctx *PolicyContext) bool {
|
||||
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)
|
||||
subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(kindsInPolicy, ctx)
|
||||
|
||||
func matches(logger logr.Logger, rule *kyvernov1.Rule, ctx *PolicyContext, subresourceGVKToAPIResource map[string]*metav1.APIResource) bool {
|
||||
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.newResource, *rule, ctx.admissionInfo, ctx.excludeGroupRole, ctx.namespaceLabels, "", ctx.subresource)
|
||||
if err == nil {
|
||||
return true
|
||||
|
@ -775,13 +775,13 @@ func (v *validator) substituteDeny() error {
|
|||
}
|
||||
|
||||
// matchesException checks if an exception applies to the resource being admitted
|
||||
func matchesException(policyContext *PolicyContext, rule *kyvernov1.Rule) (*kyvernov2alpha1.PolicyException, error) {
|
||||
func matchesException(policyContext *PolicyContext, rule *kyvernov1.Rule, subresourceGVKToAPIResource map[string]*metav1.APIResource) (*kyvernov2alpha1.PolicyException, error) {
|
||||
candidates, err := policyContext.FindExceptions(rule.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
err := matched.CheckMatchesResources(policyContext.newResource, candidate.Spec.Match, policyContext.namespaceLabels)
|
||||
err := matched.CheckMatchesResources(policyContext.newResource, candidate.Spec.Match, policyContext.namespaceLabels, subresourceGVKToAPIResource, policyContext.subresource)
|
||||
// if there's no error it means a match
|
||||
if err == nil {
|
||||
return candidate, nil
|
||||
|
@ -792,9 +792,9 @@ func matchesException(policyContext *PolicyContext, rule *kyvernov1.Rule) (*kyve
|
|||
|
||||
// hasPolicyExceptions returns nil when there are no matching exceptions.
|
||||
// A rule response is returned when an exception is matched, or there is an error.
|
||||
func hasPolicyExceptions(ctx *PolicyContext, rule *kyvernov1.Rule, log logr.Logger) *response.RuleResponse {
|
||||
func hasPolicyExceptions(ctx *PolicyContext, rule *kyvernov1.Rule, subresourceGVKToAPIResource map[string]*metav1.APIResource, log logr.Logger) *response.RuleResponse {
|
||||
// if matches, check if there is a corresponding policy exception
|
||||
exception, err := matchesException(ctx, rule)
|
||||
exception, err := matchesException(ctx, rule, subresourceGVKToAPIResource)
|
||||
// if we found an exception
|
||||
if err == nil && exception != nil {
|
||||
key, err := cache.MetaNamespaceKeyFunc(exception)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/wildcards"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
"github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/text/cases"
|
||||
|
@ -32,6 +33,8 @@ func CheckMatchesResources(
|
|||
resource unstructured.Unstructured,
|
||||
statement kyvernov2beta1.MatchResources,
|
||||
namespaceLabels map[string]string,
|
||||
subresourceGVKToAPIResource map[string]*metav1.APIResource,
|
||||
subresourceInAdmnReview string,
|
||||
// policyNamespace string,
|
||||
) error {
|
||||
var errs []error
|
||||
|
@ -45,6 +48,8 @@ func CheckMatchesResources(
|
|||
rmr,
|
||||
resource,
|
||||
namespaceLabels,
|
||||
subresourceGVKToAPIResource,
|
||||
subresourceInAdmnReview,
|
||||
)) == 0 {
|
||||
oneMatched = true
|
||||
break
|
||||
|
@ -62,6 +67,8 @@ func CheckMatchesResources(
|
|||
rmr,
|
||||
resource,
|
||||
namespaceLabels,
|
||||
subresourceGVKToAPIResource,
|
||||
subresourceInAdmnReview,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
@ -73,6 +80,8 @@ func checkResourceFilter(
|
|||
statement kyvernov1.ResourceFilter,
|
||||
resource unstructured.Unstructured,
|
||||
namespaceLabels map[string]string,
|
||||
subresourceGVKToAPIResource map[string]*metav1.APIResource,
|
||||
subresourceInAdmnReview string,
|
||||
) []error {
|
||||
var errs []error
|
||||
// checking if the block is empty
|
||||
|
@ -84,6 +93,8 @@ func checkResourceFilter(
|
|||
statement.ResourceDescription,
|
||||
resource,
|
||||
namespaceLabels,
|
||||
subresourceGVKToAPIResource,
|
||||
subresourceInAdmnReview,
|
||||
)
|
||||
errs = append(errs, matchErrs...)
|
||||
return errs
|
||||
|
@ -93,10 +104,12 @@ func checkResourceDescription(
|
|||
conditionBlock kyvernov1.ResourceDescription,
|
||||
resource unstructured.Unstructured,
|
||||
namespaceLabels map[string]string,
|
||||
subresourceGVKToAPIResource map[string]*metav1.APIResource,
|
||||
subresourceInAdmnReview string,
|
||||
) []error {
|
||||
var errs []error
|
||||
if len(conditionBlock.Kinds) > 0 {
|
||||
if !checkKind(conditionBlock.Kinds, resource.GetKind(), resource.GroupVersionKind()) {
|
||||
if !CheckKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview) {
|
||||
errs = append(errs, fmt.Errorf("kind does not match %v", conditionBlock.Kinds))
|
||||
}
|
||||
}
|
||||
|
@ -154,29 +167,30 @@ func checkResourceDescription(
|
|||
return errs
|
||||
}
|
||||
|
||||
func checkKind(kinds []string, resourceKind string, gvk schema.GroupVersionKind) bool {
|
||||
func CheckKind(subresourceGVKToAPIResource map[string]*metav1.APIResource, kinds []string, gvk schema.GroupVersionKind, subresourceInAdmnReview string) bool {
|
||||
title := cases.Title(language.Und, cases.NoLower)
|
||||
result := false
|
||||
for _, k := range kinds {
|
||||
parts := strings.Split(k, "/")
|
||||
if len(parts) == 1 {
|
||||
if k == "*" || resourceKind == title.String(k) {
|
||||
return true
|
||||
if k != "*" {
|
||||
gv, kind := kubeutils.GetKindFromGVK(k)
|
||||
apiResource, ok := subresourceGVKToAPIResource[k]
|
||||
if ok {
|
||||
result = apiResource.Group == gvk.Group && (apiResource.Version == gvk.Version || strings.Contains(gv, "*")) && apiResource.Kind == gvk.Kind
|
||||
} else { // if the kind is not found in the subresourceGVKToAPIResource, then it is not a subresource
|
||||
result = title.String(kind) == gvk.Kind && subresourceInAdmnReview == ""
|
||||
if gv != "" {
|
||||
result = result && kubeutils.GroupVersionMatches(gv, gvk.GroupVersion().String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = true
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
kindParts := strings.SplitN(parts[1], ".", 2)
|
||||
if gvk.Kind == title.String(kindParts[0]) && gvk.Version == parts[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if len(parts) == 3 || len(parts) == 4 {
|
||||
kindParts := strings.SplitN(parts[2], ".", 2)
|
||||
if gvk.Group == parts[0] && (gvk.Version == parts[1] || parts[1] == "*") && gvk.Kind == title.String(kindParts[0]) {
|
||||
return true
|
||||
}
|
||||
|
||||
if result {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
return result
|
||||
}
|
||||
|
||||
func checkName(name, resourceName string) bool {
|
||||
|
|
74
pkg/utils/match/match_test.go
Normal file
74
pkg/utils/match/match_test.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package match
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func Test_CheckKind(t *testing.T) {
|
||||
subresourceGVKToAPIResource := make(map[string]*metav1.APIResource)
|
||||
match := CheckKind(subresourceGVKToAPIResource, []string{"*"}, schema.GroupVersionKind{Kind: "Deployment", Group: "", Version: "v1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"tekton.dev/v1beta1/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1beta1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"tekton.dev/*/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1alpha1"}, "")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
// Though both 'pods', 'pods/status' have same kind i.e. 'Pod' but they are different resources, 'subresourceInAdmnReview' is used in determining that.
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status")
|
||||
assert.Equal(t, match, false)
|
||||
|
||||
// Though both 'pods', 'pods/status' have same kind i.e. 'Pod' but they are different resources, 'subresourceInAdmnReview' is used in determining that.
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers")
|
||||
assert.Equal(t, match, false)
|
||||
|
||||
subresourceGVKToAPIResource["networking.k8s.io/v1/NetworkPolicy/status"] = &metav1.APIResource{
|
||||
Name: "networkpolicies/status",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Kind: "NetworkPolicy",
|
||||
Group: "networking.k8s.io",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
subresourceGVKToAPIResource["v1/Pod.status"] = &metav1.APIResource{
|
||||
Name: "pods/status",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Kind: "Pod",
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
subresourceGVKToAPIResource["*/Pod.eviction"] = &metav1.APIResource{
|
||||
Name: "pods/eviction",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Kind: "Eviction",
|
||||
Group: "policy",
|
||||
Version: "v1",
|
||||
}
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"networking.k8s.io/v1/NetworkPolicy/status"}, schema.GroupVersionKind{Kind: "NetworkPolicy", Group: "networking.k8s.io", Version: "v1"}, "status")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod.status"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"*/Pod.eviction"}, schema.GroupVersionKind{Kind: "Eviction", Group: "policy", Version: "v1"}, "eviction")
|
||||
assert.Equal(t, match, true)
|
||||
|
||||
match = CheckKind(subresourceGVKToAPIResource, []string{"v1alpha1/Pod.eviction"}, schema.GroupVersionKind{Kind: "Eviction", Group: "policy", Version: "v1"}, "eviction")
|
||||
assert.Equal(t, match, false)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: nginx-test-scaling-policy
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-test
|
||||
name: nginx-test
|
||||
namespace: test-validate
|
||||
status:
|
||||
replicas: 2
|
||||
---
|
||||
apiVersion: kyverno.io/v2alpha1
|
||||
kind: PolicyException
|
||||
metadata:
|
||||
name: allow-scaling-nginx-test
|
||||
namespace: test-validate
|
||||
spec:
|
||||
exceptions:
|
||||
- policyName: nginx-test-scaling-policy
|
||||
ruleNames:
|
||||
- validate-nginx-test
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment/scale
|
||||
names:
|
||||
- nginx-test
|
|
@ -0,0 +1,67 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: test-validate
|
||||
---
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: nginx-test-scaling-policy
|
||||
spec:
|
||||
background: false
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- match:
|
||||
resources:
|
||||
kinds:
|
||||
- "Deployment/scale"
|
||||
names:
|
||||
- nginx-test
|
||||
namespaces:
|
||||
- test-validate
|
||||
name: validate-nginx-test
|
||||
validate:
|
||||
message: 'nginx-test needs to have 2 replicas'
|
||||
pattern:
|
||||
spec:
|
||||
replicas: 2
|
||||
validationFailureAction: Enforce
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-test
|
||||
name: nginx-test
|
||||
namespace: test-validate
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-test
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-test
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
---
|
||||
apiVersion: kyverno.io/v2alpha1
|
||||
kind: PolicyException
|
||||
metadata:
|
||||
name: allow-scaling-nginx-test
|
||||
namespace: test-validate
|
||||
spec:
|
||||
exceptions:
|
||||
- policyName: nginx-test-scaling-policy
|
||||
ruleNames:
|
||||
- validate-nginx-test
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Deployment/scale
|
||||
names:
|
||||
- nginx-test
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
commands:
|
||||
- script: |
|
||||
if kubectl scale deployment nginx-test --replicas=1 -n test-validate 2>&1 | grep -q 'validation error: nginx-test needs to have 2 replicas'
|
||||
then
|
||||
echo "Test failed. Resource was blocked from scaling."
|
||||
exit 1
|
||||
else
|
||||
echo "Tested succeeded. Resource was allowed to scale."
|
||||
exit 0
|
||||
fi
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
commands:
|
||||
- command: kubectl delete deploy nginx-test -n test-validate --force --wait=true --ignore-not-found=true
|
||||
- command: kubectl delete polex allow-scaling-nginx-test -n test-validate --force --wait=true --ignore-not-found=true
|
||||
- command: kubectl delete cpol nginx-test-scaling-policy -n test-validate --force --wait=true --ignore-not-found=true
|
||||
- command: kubectl delete ns test-validate --force --ignore-not-found=true
|
|
@ -0,0 +1,20 @@
|
|||
# ## Description
|
||||
|
||||
This test validates that a policy blocking scaling using `Deployment/scale` resource can be bypassed using `PolicyException`.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
The `Deployment` is scaled.
|
||||
|
||||
## Steps
|
||||
|
||||
### Test Steps
|
||||
|
||||
1. Create a `ClusterPolicy` that matches on `Deployment/scale` and blocks scaling the `Deployment`.
|
||||
2. Create a `Deployment` with the number of replicas allowed in the policy.
|
||||
3. Create a `PolicyException` for the above mentioned policy.
|
||||
4. Validate that the `Deployment` is scaled.
|
||||
|
||||
## Reference Issue(s)
|
||||
|
||||
https://github.com/kyverno/kyverno/issues/5804
|
Loading…
Reference in a new issue