diff --git a/cmd/cleanup-controller/handlers/cleanup/handlers.go b/cmd/cleanup-controller/handlers/cleanup/handlers.go index b2dff73a0c..cf3d10cc57 100644 --- a/cmd/cleanup-controller/handlers/cleanup/handlers.go +++ b/cmd/cleanup-controller/handlers/cleanup/handlers.go @@ -149,12 +149,12 @@ func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy resource, spec.MatchResources, nsLabels, - nil, - "", // TODO(eddycharly): we don't have user info here, we should check that // we don't have user conditions in the policy rule kyvernov1beta1.RequestInfo{}, nil, + resource.GroupVersionKind(), + "", ) if matched != nil { debug.Info("resource/match didn't match", "result", matched) @@ -165,12 +165,12 @@ func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy resource, *spec.ExcludeResources, nsLabels, - nil, - "", // TODO(eddycharly): we don't have user info here, we should check that // we don't have user conditions in the policy rule kyvernov1beta1.RequestInfo{}, nil, + resource.GroupVersionKind(), + "", ) if excluded == nil { debug.Info("resource/exclude matched") diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index d58aff7999..65a6d571e5 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -33,6 +33,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -457,17 +458,24 @@ OuterLoop: } } - subresources := make([]engineapi.SubResource, 0) - - // If --cluster flag is not set, then we need to add subresources to the context + gvk, subresource := updatedResource.GroupVersionKind(), "" + // If --cluster flag is not set, then we need to find the top level resource GVK and subresource if c.Client == nil { - for _, subresource := range c.Subresources { - subresources = append(subresources, struct { - APIResource metav1.APIResource - ParentResource metav1.APIResource - }{ - APIResource: subresource.APIResource, ParentResource: subresource.ParentResource, - }) + for _, s := range c.Subresources { + subgvk := schema.GroupVersionKind{ + Group: s.APIResource.Group, + Version: s.APIResource.Version, + Kind: s.APIResource.Kind, + } + if gvk == subgvk { + gvk = schema.GroupVersionKind{ + Group: s.ParentResource.Group, + Version: s.ParentResource.Version, + Kind: s.ParentResource.Kind, + } + parts := strings.Split(s.APIResource.Name, "/") + subresource = parts[1] + } } } eng := engine.NewEngine( @@ -482,7 +490,7 @@ OuterLoop: WithNewResource(*updatedResource). WithNamespaceLabels(namespaceLabels). WithAdmissionInfo(c.UserInfo). - WithSubresourcesInPolicy(subresources) + WithResourceKind(gvk, subresource) mutateResponse := eng.Mutate( context.Background(), diff --git a/pkg/engine/api/policycontext.go b/pkg/engine/api/policycontext.go index a3446260e9..d958c43ed5 100644 --- a/pkg/engine/api/policycontext.go +++ b/pkg/engine/api/policycontext.go @@ -6,26 +6,21 @@ import ( enginecontext "github.com/kyverno/kyverno/pkg/engine/context" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" ) // ExcludeFunc is a function used to determine if a resource is excluded type ExcludeFunc = func(kind, namespace, name string) bool -type SubResource struct { - APIResource metav1.APIResource - ParentResource metav1.APIResource -} - type PolicyContext interface { Policy() kyvernov1.PolicyInterface NewResource() unstructured.Unstructured OldResource() unstructured.Unstructured AdmissionInfo() kyvernov1beta1.RequestInfo NamespaceLabels() map[string]string - SubResource() string - SubresourcesInPolicy() []SubResource - AdmissionOperation() bool RequestResource() metav1.GroupVersionResource + ResourceKind() (schema.GroupVersionKind, string) + AdmissionOperation() bool Element() unstructured.Unstructured SetElement(element unstructured.Unstructured) diff --git a/pkg/engine/background.go b/pkg/engine/background.go index 485e1cd752..7087b5e105 100644 --- a/pkg/engine/background.go +++ b/pkg/engine/background.go @@ -73,16 +73,13 @@ func (e *engine) filterRule( return nil } - kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...) - subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(e.client, kindsInPolicy, policyContext) - ruleType := engineapi.Mutation if rule.HasGenerate() { ruleType = engineapi.Generation } // check if there is a corresponding policy exception - ruleResp := hasPolicyExceptions(logger, ruleType, e.exceptionSelector, policyContext, &rule, subresourceGVKToAPIResource, e.configuration) + ruleResp := hasPolicyExceptions(logger, ruleType, e.exceptionSelector, policyContext, &rule, e.configuration) if ruleResp != nil { return ruleResp } @@ -96,11 +93,21 @@ func (e *engine) filterRule( excludeGroupRole := e.configuration.GetExcludedGroups() namespaceLabels := policyContext.NamespaceLabels() policy := policyContext.Policy() + gvk, subresource := policyContext.ResourceKind() - if err := MatchesResourceDescription(subresourceGVKToAPIResource, newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, policy.GetNamespace(), policyContext.SubResource()); err != nil { + if err := MatchesResourceDescription( + newResource, + rule, + admissionInfo, + excludeGroupRole, + namespaceLabels, + policy.GetNamespace(), + gvk, + subresource, + ); err != nil || newResource.Object == nil { if ruleType == engineapi.Generation { // if the oldResource matched, return "false" to delete GR for it - if err = MatchesResourceDescription(subresourceGVKToAPIResource, oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, policy.GetNamespace(), policyContext.SubResource()); err == nil { + if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, policy.GetNamespace(), gvk, subresource); err == nil { return &engineapi.RuleResponse{ Name: rule.Name, Type: ruleType, diff --git a/pkg/engine/common.go b/pkg/engine/common.go deleted file mode 100644 index 1b88644676..0000000000 --- a/pkg/engine/common.go +++ /dev/null @@ -1,65 +0,0 @@ -package engine - -import ( - "strings" - - "github.com/kyverno/kyverno/pkg/clients/dclient" - engineapi "github.com/kyverno/kyverno/pkg/engine/api" - kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetSubresourceGVKToAPIResourceMap returns a map of subresource GVK to APIResource. This is used to determine if a resource is a subresource. -func GetSubresourceGVKToAPIResourceMap(client dclient.Interface, kindsInPolicy []string, ctx engineapi.PolicyContext) map[string]*metav1.APIResource { - subresourceGVKToAPIResource := make(map[string]*metav1.APIResource) - for _, gvk := range kindsInPolicy { - gv, k := kubeutils.GetKindFromGVK(gvk) - parentKind, subresource := kubeutils.SplitSubresource(k) - // Len of subresources is non zero only when validation request was sent from CLI without connecting to the cluster. - subresourcesInPolicy := ctx.SubresourcesInPolicy() - if len(subresourcesInPolicy) != 0 { - if subresource != "" { - for _, subresourceInPolicy := range subresourcesInPolicy { - parentResourceGroupVersion := metav1.GroupVersion{ - Group: subresourceInPolicy.ParentResource.Group, - Version: subresourceInPolicy.ParentResource.Version, - }.String() - if gv == "" || kubeutils.GroupVersionMatches(gv, parentResourceGroupVersion) { - if parentKind == subresourceInPolicy.ParentResource.Kind { - if strings.ToLower(subresource) == strings.Split(subresourceInPolicy.APIResource.Name, "/")[1] { - subresourceGVKToAPIResource[gvk] = &(subresourceInPolicy.APIResource) - break - } - } - } - } - } else { // Complete kind may be a subresource, for eg- 'PodExecOptions' - for _, subresourceInPolicy := range subresourcesInPolicy { - // Subresources which can be just specified by kind, for eg- 'PodExecOptions' - // have different kind than their parent resource. Otherwise for subresources which - // have same kind as parent resource, need to be specified as Kind/Subresource, eg - 'Pod/status' - if k == subresourceInPolicy.APIResource.Kind && - k != subresourceInPolicy.ParentResource.Kind { - subresourceGroupVersion := metav1.GroupVersion{ - Group: subresourceInPolicy.APIResource.Group, - Version: subresourceInPolicy.APIResource.Version, - }.String() - if gv == "" || kubeutils.GroupVersionMatches(gv, subresourceGroupVersion) { - subresourceGVKToAPIResource[gvk] = subresourceInPolicy.APIResource.DeepCopy() - break - } - } - } - } - } else if client != nil { - // find the resource from API client - apiResource, _, _, err := client.Discovery().FindResource(gv, k) - if err == nil { - if kubeutils.IsSubresource(apiResource.Name) { - subresourceGVKToAPIResource[gvk] = apiResource - } - } - } - } - return subresourceGVKToAPIResource -} diff --git a/pkg/engine/common_test.go b/pkg/engine/common_test.go deleted file mode 100644 index 2c50a962db..0000000000 --- a/pkg/engine/common_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package engine - -import ( - "testing" - - engineapi "github.com/kyverno/kyverno/pkg/engine/api" - "gotest.tools/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func Test_GetSubresourceGVKToAPIResourceMap(t *testing.T) { - - podAPIResource := metav1.APIResource{ - Name: "pods", - SingularName: "", - Namespaced: true, - Kind: "Pod", - Group: "", - Version: "v1", - } - - podStatusAPIResource := metav1.APIResource{ - Name: "pods/status", - SingularName: "", - Namespaced: true, - Kind: "Pod", - Group: "", - Version: "v1", - } - - podEvictAPIResource := metav1.APIResource{ - Name: "pods/eviction", - SingularName: "", - Namespaced: true, - Kind: "Eviction", - Group: "policy", - Version: "v1", - } - - policyContext := NewPolicyContext(). - WithSubresourcesInPolicy([]engineapi.SubResource{ - { - APIResource: podStatusAPIResource, - ParentResource: podAPIResource, - }, - { - APIResource: podEvictAPIResource, - ParentResource: podAPIResource, - }, - }) - - kindsInPolicy := []string{"Pod", "Eviction", "Pod/status", "Pod/eviction"} - - subresourceGVKToAPIResourceMap := GetSubresourceGVKToAPIResourceMap(nil, kindsInPolicy, policyContext) - - podStatusResourceFromMap := subresourceGVKToAPIResourceMap["Pod/status"] - assert.Equal(t, podStatusResourceFromMap.Name, podStatusAPIResource.Name) - assert.Equal(t, podStatusResourceFromMap.Kind, podStatusAPIResource.Kind) - assert.Equal(t, podStatusResourceFromMap.Group, podStatusAPIResource.Group) - assert.Equal(t, podStatusResourceFromMap.Version, podStatusAPIResource.Version) - - podEvictResourceFromMap := subresourceGVKToAPIResourceMap["Pod/eviction"] - assert.Equal(t, podEvictResourceFromMap.Name, podEvictAPIResource.Name) - assert.Equal(t, podEvictResourceFromMap.Kind, podEvictAPIResource.Kind) - assert.Equal(t, podEvictResourceFromMap.Group, podEvictAPIResource.Group) - assert.Equal(t, podEvictResourceFromMap.Version, podEvictAPIResource.Version) - - podEvictResourceFromMap = subresourceGVKToAPIResourceMap["Eviction"] - assert.Equal(t, podEvictResourceFromMap.Name, podEvictAPIResource.Name) - assert.Equal(t, podEvictResourceFromMap.Kind, podEvictAPIResource.Kind) - assert.Equal(t, podEvictResourceFromMap.Group, podEvictAPIResource.Group) - assert.Equal(t, podEvictResourceFromMap.Version, podEvictAPIResource.Version) - - _, ok := subresourceGVKToAPIResourceMap["Pod"] - assert.Equal(t, ok, false) -} diff --git a/pkg/engine/exceptions.go b/pkg/engine/exceptions.go index 7944c058af..ebcf14f8a0 100644 --- a/pkg/engine/exceptions.go +++ b/pkg/engine/exceptions.go @@ -10,7 +10,6 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/internal" matched "github.com/kyverno/kyverno/pkg/utils/match" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" ) @@ -45,22 +44,22 @@ func matchesException( selector engineapi.PolicyExceptionSelector, policyContext engineapi.PolicyContext, rule *kyvernov1.Rule, - subresourceGVKToAPIResource map[string]*metav1.APIResource, cfg config.Configuration, ) (*kyvernov2alpha1.PolicyException, error) { candidates, err := findExceptions(selector, policyContext.Policy(), rule.Name) if err != nil { return nil, err } + gvk, subresource := policyContext.ResourceKind() for _, candidate := range candidates { err := matched.CheckMatchesResources( policyContext.NewResource(), candidate.Spec.Match, policyContext.NamespaceLabels(), - subresourceGVKToAPIResource, - policyContext.SubResource(), policyContext.AdmissionInfo(), cfg.GetExcludedGroups(), + gvk, + subresource, ) // if there's no error it means a match if err == nil { @@ -78,11 +77,10 @@ func hasPolicyExceptions( selector engineapi.PolicyExceptionSelector, ctx engineapi.PolicyContext, rule *kyvernov1.Rule, - subresourceGVKToAPIResource map[string]*metav1.APIResource, cfg config.Configuration, ) *engineapi.RuleResponse { // if matches, check if there is a corresponding policy exception - exception, err := matchesException(selector, ctx, rule, subresourceGVKToAPIResource, cfg) + exception, err := matchesException(selector, ctx, rule, cfg) var response *engineapi.RuleResponse // if we found an exception if err == nil && exception != nil { diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index 6ec92d983c..c316065a4a 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -74,15 +74,13 @@ func (e *engine) doVerifyAndPatch( } startTime := time.Now() logger = internal.LoggerWithRule(logger, *rule) - kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...) - subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(e.client, kindsInPolicy, policyContext) - if !matches(logger, rule, policyContext, subresourceGVKToAPIResource, e.configuration) { + if !matches(logger, rule, policyContext, e.configuration) { return } // check if there is a corresponding policy exception - ruleResp := hasPolicyExceptions(logger, engineapi.ImageVerify, e.exceptionSelector, policyContext, rule, subresourceGVKToAPIResource, e.configuration) + ruleResp := hasPolicyExceptions(logger, engineapi.ImageVerify, e.exceptionSelector, policyContext, rule, e.configuration) if ruleResp != nil { resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) return diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index b67b1213f1..4faac55c80 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -58,17 +58,24 @@ func (e *engine) mutate( if len(e.configuration.GetExcludedGroups()) > 0 { excludeResource = e.configuration.GetExcludedGroups() } - - kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...) - subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(e.client, kindsInPolicy, policyContext) - if err = MatchesResourceDescription(subresourceGVKToAPIResource, matchedResource, rule, policyContext.AdmissionInfo(), excludeResource, policyContext.NamespaceLabels(), policyContext.Policy().GetNamespace(), policyContext.SubResource()); err != nil { + gvk, subresource := policyContext.ResourceKind() + if err = MatchesResourceDescription( + matchedResource, + rule, + policyContext.AdmissionInfo(), + excludeResource, + policyContext.NamespaceLabels(), + policyContext.Policy().GetNamespace(), + gvk, + subresource, + ); err != nil { logger.V(4).Info("rule not matched", "reason", err.Error()) skippedRules = append(skippedRules, rule.Name) return } // check if there is a corresponding policy exception - if ruleResp := hasPolicyExceptions(logger, engineapi.Mutation, e.exceptionSelector, policyContext, &computeRules[i], subresourceGVKToAPIResource, e.configuration); ruleResp != nil { + if ruleResp := hasPolicyExceptions(logger, engineapi.Mutation, e.exceptionSelector, policyContext, &computeRules[i], e.configuration); ruleResp != nil { resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp) return } @@ -96,12 +103,12 @@ func (e *engine) mutate( } } else { var parentResourceGVR metav1.GroupVersionResource - if policyContext.SubResource() != "" { + if subresource != "" { parentResourceGVR = policyContext.RequestResource() } patchedResources = append(patchedResources, resourceInfo{ unstructured: matchedResource, - subresource: policyContext.SubResource(), + subresource: subresource, parentResourceGVR: parentResourceGVR, }) } diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index a9b96c5fdb..f4ec7b2de4 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -5,6 +5,7 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" engineapi "github.com/kyverno/kyverno/pkg/engine/api" enginectx "github.com/kyverno/kyverno/pkg/engine/context" @@ -12,6 +13,7 @@ import ( admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" ) // PolicyContext contains the contexts for engine to process @@ -31,16 +33,15 @@ type PolicyContext struct { // admissionInfo contains the admission request information admissionInfo kyvernov1beta1.RequestInfo - // requestResource is the fully-qualified resource of the original API request (for example, v1.pods). - // If this is specified and differs from the value in "resource", an equivalent match and conversion was performed. - // - // For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - // `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - // an API request to apps/v1beta1 deployments would be converted and sent to the webhook - // with `resource: {group:"apps", version:"v1", resource:"deployments"}` (matching the resource the webhook registered for), - // and `requestResource: {group:"apps", version:"v1beta1", resource:"deployments"}` (indicating the resource of the original API request). + // requestResource is GVR of the admission request requestResource metav1.GroupVersionResource + // gvk is GVK of the top level resource + gvk schema.GroupVersionKind + + // subresource is the subresource being requested, if any (for example, "status" or "scale") + subresource string + // jsonContext is the variable context jsonContext enginectx.Interface @@ -49,14 +50,6 @@ type PolicyContext struct { // admissionOperation represents if the caller is from the webhook server admissionOperation bool - - // subresource is the subresource being requested, if any (for example, "status" or "scale") - subresource string - - // subresourcesInPolicy represents the APIResources that are subresources along with their parent resource. - // This is used to determine if a resource is a subresource. It is only used when the policy context is populated - // by kyverno CLI. In all other cases when connected to a cluster, this is empty. - subresourcesInPolicy []engineapi.SubResource } // engineapi.PolicyContext interface @@ -73,6 +66,18 @@ func (c *PolicyContext) OldResource() unstructured.Unstructured { return c.oldResource } +func (c *PolicyContext) RequestResource() metav1.GroupVersionResource { + return c.requestResource +} + +func (c *PolicyContext) ResourceKind() (schema.GroupVersionKind, string) { + // TODO: fallback + if c.gvk.Empty() { + return c.newResource.GroupVersionKind(), "" + } + return c.gvk, c.subresource +} + func (c *PolicyContext) AdmissionInfo() kyvernov1beta1.RequestInfo { return c.admissionInfo } @@ -81,22 +86,10 @@ func (c *PolicyContext) NamespaceLabels() map[string]string { return c.namespaceLabels } -func (c *PolicyContext) SubResource() string { - return c.subresource -} - -func (c *PolicyContext) SubresourcesInPolicy() []engineapi.SubResource { - return c.subresourcesInPolicy -} - func (c *PolicyContext) AdmissionOperation() bool { return c.admissionOperation } -func (c *PolicyContext) RequestResource() metav1.GroupVersionResource { - return c.requestResource -} - func (c *PolicyContext) Element() unstructured.Unstructured { return c.element } @@ -133,12 +126,6 @@ func (c *PolicyContext) WithAdmissionInfo(admissionInfo kyvernov1beta1.RequestIn return copy } -func (c *PolicyContext) WithRequestResource(requestResource metav1.GroupVersionResource) *PolicyContext { - copy := c.copy() - copy.requestResource = requestResource - return copy -} - func (c *PolicyContext) WithNewResource(resource unstructured.Unstructured) *PolicyContext { copy := c.copy() copy.newResource = resource @@ -151,6 +138,19 @@ func (c *PolicyContext) WithOldResource(resource unstructured.Unstructured) *Pol return copy } +func (c *PolicyContext) WithResourceKind(gvk schema.GroupVersionKind, subresource string) *PolicyContext { + copy := c.copy() + copy.gvk = gvk + copy.subresource = subresource + return copy +} + +func (c *PolicyContext) WithRequestResource(gvr metav1.GroupVersionResource) *PolicyContext { + copy := c.copy() + copy.requestResource = gvr + return copy +} + func (c *PolicyContext) WithResources(newResource unstructured.Unstructured, oldResource unstructured.Unstructured) *PolicyContext { return c.WithNewResource(newResource).WithOldResource(oldResource) } @@ -161,18 +161,6 @@ func (c *PolicyContext) withAdmissionOperation(admissionOperation bool) *PolicyC return copy } -func (c *PolicyContext) WithSubresource(subresource string) *PolicyContext { - copy := c.copy() - copy.subresource = subresource - return copy -} - -func (c *PolicyContext) WithSubresourcesInPolicy(subresourcesInPolicy []engineapi.SubResource) *PolicyContext { - copy := c.copy() - copy.subresourcesInPolicy = subresourcesInPolicy - return copy -} - func (c PolicyContext) copy() *PolicyContext { return &c } @@ -190,6 +178,7 @@ func NewPolicyContext() *PolicyContext { } func NewPolicyContextFromAdmissionRequest( + client dclient.IDiscovery, request *admissionv1.AdmissionRequest, admissionInfo kyvernov1beta1.RequestInfo, configuration config.Configuration, @@ -205,14 +194,17 @@ func NewPolicyContextFromAdmissionRequest( if err := ctx.AddImageInfos(&newResource, configuration); err != nil { return nil, fmt.Errorf("failed to add image information to the policy rule context: %w", err) } - requestResource := request.RequestResource.DeepCopy() + gvk, err := client.GetGVKFromGVR(schema.GroupVersionResource(request.Resource)) + if err != nil { + return nil, err + } policyContext := NewPolicyContextWithJsonContext(ctx). WithNewResource(newResource). WithOldResource(oldResource). WithAdmissionInfo(admissionInfo). withAdmissionOperation(true). - WithRequestResource(*requestResource). - WithSubresource(request.SubResource) + WithResourceKind(gvk, request.SubResource). + WithRequestResource(request.Resource) return policyContext, nil } diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 5a7f50ceb5..e81c7c8457 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -14,8 +14,8 @@ import ( "golang.org/x/exp/slices" authenticationv1 "k8s.io/api/authentication/v1" rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" ) func checkNameSpace(namespaces []string, resource unstructured.Unstructured) bool { @@ -52,12 +52,20 @@ func checkNameSpace(namespaces []string, resource unstructured.Unstructured) boo // should be: AND across attributes but an OR inside attributes that of type list // To filter out the targeted resources with UserInfo, the check // should be: OR (across & inside) attributes -func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*metav1.APIResource, conditionBlock kyvernov1.ResourceDescription, userInfo kyvernov1.UserInfo, admissionInfo kyvernov1beta1.RequestInfo, resource unstructured.Unstructured, namespaceLabels map[string]string, subresourceInAdmnReview string) []error { +func doesResourceMatchConditionBlock( + conditionBlock kyvernov1.ResourceDescription, + userInfo kyvernov1.UserInfo, + admissionInfo kyvernov1beta1.RequestInfo, + resource unstructured.Unstructured, + namespaceLabels map[string]string, + gvk schema.GroupVersionKind, + subresource string, +) []error { var errs []error if len(conditionBlock.Kinds) > 0 { // Matching on ephemeralcontainers even when they are not explicitly specified for backward compatibility. - if !matchutils.CheckKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview, true) { + if !matchutils.CheckKind(conditionBlock.Kinds, gvk, subresource, true) { errs = append(errs, fmt.Errorf("kind does not match %v", conditionBlock.Kinds)) } } @@ -148,7 +156,16 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User } // MatchesResourceDescription checks if the resource matches resource description of the rule or not -func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.APIResource, resourceRef unstructured.Unstructured, ruleRef kyvernov1.Rule, admissionInfoRef kyvernov1beta1.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string, policyNamespace, subresourceInAdmnReview string) error { +func MatchesResourceDescription( + resourceRef unstructured.Unstructured, + ruleRef kyvernov1.Rule, + admissionInfoRef kyvernov1beta1.RequestInfo, + dynamicConfig []string, + namespaceLabels map[string]string, + policyNamespace string, + gvk schema.GroupVersionKind, + subresource string, +) error { rule := ruleRef.DeepCopy() resource := *resourceRef.DeepCopy() admissionInfo := *admissionInfoRef.DeepCopy() @@ -165,7 +182,7 @@ func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.A oneMatched := false for _, rmr := range rule.MatchResources.Any { // if there are no errors it means it was a match - if len(matchesResourceDescriptionMatchHelper(subresourceGVKToAPIResource, rmr, admissionInfo, resource, empty, namespaceLabels, subresourceInAdmnReview)) == 0 { + if len(matchesResourceDescriptionMatchHelper(rmr, admissionInfo, resource, empty, namespaceLabels, gvk, subresource)) == 0 { oneMatched = true break } @@ -176,17 +193,17 @@ func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.A } else if len(rule.MatchResources.All) > 0 { // include object if ALL of the criteria match for _, rmr := range rule.MatchResources.All { - reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionMatchHelper(subresourceGVKToAPIResource, rmr, admissionInfo, resource, empty, namespaceLabels, subresourceInAdmnReview)...) + reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionMatchHelper(rmr, admissionInfo, resource, empty, namespaceLabels, gvk, subresource)...) } } else { rmr := kyvernov1.ResourceFilter{UserInfo: rule.MatchResources.UserInfo, ResourceDescription: rule.MatchResources.ResourceDescription} - reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionMatchHelper(subresourceGVKToAPIResource, rmr, admissionInfo, resource, empty, namespaceLabels, subresourceInAdmnReview)...) + reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionMatchHelper(rmr, admissionInfo, resource, empty, namespaceLabels, gvk, subresource)...) } if len(rule.ExcludeResources.Any) > 0 { // exclude the object if ANY of the criteria match for _, rer := range rule.ExcludeResources.Any { - reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(subresourceGVKToAPIResource, rer, admissionInfo, resource, dynamicConfig, namespaceLabels, subresourceInAdmnReview)...) + reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, dynamicConfig, namespaceLabels, gvk, subresource)...) } } else if len(rule.ExcludeResources.All) > 0 { // exclude the object if ALL the criteria match @@ -194,7 +211,7 @@ func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.A for _, rer := range rule.ExcludeResources.All { // we got no errors inplying a resource did NOT exclude it // "matchesResourceDescriptionExcludeHelper" returns errors if resource is excluded by a filter - if len(matchesResourceDescriptionExcludeHelper(subresourceGVKToAPIResource, rer, admissionInfo, resource, dynamicConfig, namespaceLabels, subresourceInAdmnReview)) == 0 { + if len(matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, dynamicConfig, namespaceLabels, gvk, subresource)) == 0 { excludedByAll = false break } @@ -204,7 +221,7 @@ func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.A } } else { rer := kyvernov1.ResourceFilter{UserInfo: rule.ExcludeResources.UserInfo, ResourceDescription: rule.ExcludeResources.ResourceDescription} - reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(subresourceGVKToAPIResource, rer, admissionInfo, resource, dynamicConfig, namespaceLabels, subresourceInAdmnReview)...) + reasonsForFailure = append(reasonsForFailure, matchesResourceDescriptionExcludeHelper(rer, admissionInfo, resource, dynamicConfig, namespaceLabels, gvk, subresource)...) } // creating final error @@ -222,7 +239,15 @@ func MatchesResourceDescription(subresourceGVKToAPIResource map[string]*metav1.A return nil } -func matchesResourceDescriptionMatchHelper(subresourceGVKToAPIResource map[string]*metav1.APIResource, rmr kyvernov1.ResourceFilter, admissionInfo kyvernov1beta1.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string, subresourceInAdmnReview string) []error { +func matchesResourceDescriptionMatchHelper( + rmr kyvernov1.ResourceFilter, + admissionInfo kyvernov1beta1.RequestInfo, + resource unstructured.Unstructured, + dynamicConfig []string, + namespaceLabels map[string]string, + gvk schema.GroupVersionKind, + subresource string, +) []error { var errs []error if reflect.DeepEqual(admissionInfo, kyvernov1beta1.RequestInfo{}) { rmr.UserInfo = kyvernov1.UserInfo{} @@ -231,7 +256,7 @@ func matchesResourceDescriptionMatchHelper(subresourceGVKToAPIResource map[strin // checking if resource matches the rule if !reflect.DeepEqual(rmr.ResourceDescription, kyvernov1.ResourceDescription{}) || !reflect.DeepEqual(rmr.UserInfo, kyvernov1.UserInfo{}) { - matchErrs := doesResourceMatchConditionBlock(subresourceGVKToAPIResource, rmr.ResourceDescription, rmr.UserInfo, admissionInfo, resource, namespaceLabels, subresourceInAdmnReview) + matchErrs := doesResourceMatchConditionBlock(rmr.ResourceDescription, rmr.UserInfo, admissionInfo, resource, namespaceLabels, gvk, subresource) errs = append(errs, matchErrs...) } else { errs = append(errs, fmt.Errorf("match cannot be empty")) @@ -239,12 +264,20 @@ func matchesResourceDescriptionMatchHelper(subresourceGVKToAPIResource map[strin return errs } -func matchesResourceDescriptionExcludeHelper(subresourceGVKToAPIResource map[string]*metav1.APIResource, rer kyvernov1.ResourceFilter, admissionInfo kyvernov1beta1.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string, subresourceInAdmnReview string) []error { +func matchesResourceDescriptionExcludeHelper( + rer kyvernov1.ResourceFilter, + admissionInfo kyvernov1beta1.RequestInfo, + resource unstructured.Unstructured, + dynamicConfig []string, + namespaceLabels map[string]string, + gvk schema.GroupVersionKind, + subresource string, +) []error { var errs []error // checking if resource matches the rule if !reflect.DeepEqual(rer.ResourceDescription, kyvernov1.ResourceDescription{}) || !reflect.DeepEqual(rer.UserInfo, kyvernov1.UserInfo{}) { - excludeErrs := doesResourceMatchConditionBlock(subresourceGVKToAPIResource, rer.ResourceDescription, rer.UserInfo, admissionInfo, resource, namespaceLabels, subresourceInAdmnReview) + excludeErrs := doesResourceMatchConditionBlock(rer.ResourceDescription, rer.UserInfo, admissionInfo, resource, namespaceLabels, gvk, subresource) // it was a match so we want to exclude it if len(excludeErrs) == 0 { errs = append(errs, fmt.Errorf("resource excluded since one of the criteria excluded it")) diff --git a/pkg/engine/utils_test.go b/pkg/engine/utils_test.go index 74bc35aa6f..0e78ffb2d4 100644 --- a/pkg/engine/utils_test.go +++ b/pkg/engine/utils_test.go @@ -906,7 +906,7 @@ func TestMatchesResourceDescription(t *testing.T) { resource, _ := kubeutils.BytesToUnstructured(tc.Resource) for _, rule := range autogen.ComputeRules(&policy) { - err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, tc.AdmissionInfo, []string{}, nil, "", "") + err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "", resource.GroupVersionKind(), "") if err != nil { if !tc.areErrorsExpected { t.Errorf("Testcase %d Unexpected error: %v\nmsg: %s", i+1, err, tc.Description) @@ -1811,7 +1811,7 @@ func TestMatchesResourceDescription_GenerateName(t *testing.T) { resource, _ := kubeutils.BytesToUnstructured(tc.Resource) for _, rule := range autogen.ComputeRules(&policy) { - err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, tc.AdmissionInfo, []string{}, nil, "", "") + err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "", resource.GroupVersionKind(), "") if err != nil { if !tc.areErrorsExpected { t.Errorf("Testcase %d Unexpected error: %v\nmsg: %s", i+1, err, tc.Description) @@ -1878,7 +1878,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -1990,7 +1990,7 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) { } // First test: confirm that this above rule produces errors (and raise an error if err == nil) - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, requestInfo, dynamicConfig, nil, "", ""); err == nil { + if err := MatchesResourceDescription(*resource, rule, requestInfo, dynamicConfig, nil, "", resource.GroupVersionKind(), ""); err == nil { t.Error("Testcase was expected to fail, but err was nil") } @@ -2010,7 +2010,7 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) { } // Second test: confirm that matching this rule does not create any errors (and raise if err != nil) - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule2, requestInfo, dynamicConfig, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule2, requestInfo, dynamicConfig, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase was expected to not fail, but err was %s", err) } @@ -2024,7 +2024,7 @@ func TestResourceDescriptionMatch_ExcludeDefaultGroups(t *testing.T) { }} // Third test: confirm that now the custom exclude-snippet should run in CheckSubjects() and that should result in this rule failing (raise if err == nil for that reason) - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule2, requestInfo, dynamicConfig, nil, "", ""); err == nil { + if err := MatchesResourceDescription(*resource, rule2, requestInfo, dynamicConfig, nil, "", resource.GroupVersionKind(), ""); err == nil { t.Error("Testcase was expected to fail, but err was nil #1!") } } @@ -2083,7 +2083,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -2141,7 +2141,7 @@ func TestResourceDescriptionMatch_GenerateName(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -2200,7 +2200,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -2258,7 +2258,7 @@ func TestResourceDescriptionMatch_GenerateName_Regex(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -2325,7 +2325,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -2393,7 +2393,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) { } rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription}} - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err != nil { t.Errorf("Testcase has failed due to the following:%v", err) } } @@ -2474,7 +2474,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) { ExcludeResources: v1.MatchResources{ResourceDescription: resourceDescriptionExclude}, } - if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err == nil { + if err := MatchesResourceDescription(*resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", resource.GroupVersionKind(), ""); err == nil { t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail") } } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 1f32025b69..f8a2d2b877 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -86,14 +86,12 @@ func (e *engine) validateResource( if !hasValidate && !hasValidateImage { return nil } - kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...) - subresourceGVKToAPIResource := GetSubresourceGVKToAPIResourceMap(e.client, kindsInPolicy, enginectx) - if !matches(logger, rule, enginectx, subresourceGVKToAPIResource, e.configuration) { + if !matches(logger, rule, enginectx, e.configuration) { return nil } // check if there is a corresponding policy exception - ruleResp := hasPolicyExceptions(logger, engineapi.Validation, e.exceptionSelector, enginectx, rule, subresourceGVKToAPIResource, e.configuration) + ruleResp := hasPolicyExceptions(logger, engineapi.Validation, e.exceptionSelector, enginectx, rule, e.configuration) if ruleResp != nil { return ruleResp } @@ -508,21 +506,38 @@ func matches( logger logr.Logger, rule *kyvernov1.Rule, ctx engineapi.PolicyContext, - subresourceGVKToAPIResource map[string]*metav1.APIResource, cfg config.Configuration, ) bool { - err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.NewResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludedGroups(), ctx.NamespaceLabels(), "", ctx.SubResource()) + gvk, subresource := ctx.ResourceKind() + err := MatchesResourceDescription( + ctx.NewResource(), + *rule, + ctx.AdmissionInfo(), + cfg.GetExcludedGroups(), + ctx.NamespaceLabels(), + "", + gvk, + subresource, + ) if err == nil { return true } oldResource := ctx.OldResource() if oldResource.Object != nil { - err := MatchesResourceDescription(subresourceGVKToAPIResource, oldResource, *rule, ctx.AdmissionInfo(), cfg.GetExcludedGroups(), ctx.NamespaceLabels(), "", ctx.SubResource()) + err := MatchesResourceDescription( + ctx.OldResource(), + *rule, + ctx.AdmissionInfo(), + cfg.GetExcludedGroups(), + ctx.NamespaceLabels(), + "", + gvk, + subresource, + ) if err == nil { return true } } - logger.V(5).Info("resource does not match rule", "reason", err.Error()) return false } diff --git a/pkg/utils/match/kind.go b/pkg/utils/match/kind.go index 39bdfc112b..35924c3228 100644 --- a/pkg/utils/match/kind.go +++ b/pkg/utils/match/kind.go @@ -1,38 +1,26 @@ package match import ( - "strings" - kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/kyverno/kyverno/pkg/utils/wildcard" "k8s.io/apimachinery/pkg/runtime/schema" ) // CheckKind checks if the resource kind matches the kinds in the policy. If the policy matches on subresources, then those resources are // present in the subresourceGVKToAPIResource map. Set allowEphemeralContainers to true to allow ephemeral containers to be matched even when the // policy does not explicitly match on ephemeral containers and only matches on pods. -func CheckKind(subresourceGVKToAPIResource map[string]*metav1.APIResource, kinds []string, gvk schema.GroupVersionKind, subresourceInAdmnReview string, allowEphemeralContainers bool) bool { - result := false +func CheckKind(kinds []string, gvk schema.GroupVersionKind, subresource string, allowEphemeralContainers bool) bool { 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 = kind == gvk.Kind && - (subresourceInAdmnReview == "" || - (allowEphemeralContainers && subresourceInAdmnReview == "ephemeralcontainers")) - if gv != "" { - result = result && kubeutils.GroupVersionMatches(gv, gvk.GroupVersion().String()) - } - } - } else { - result = true + group, version, kind, sub := kubeutils.ParseKindSelector(k) + if wildcard.Match(group, gvk.Group) && wildcard.Match(version, gvk.Version) && wildcard.Match(kind, gvk.Kind) && wildcard.Match(sub, subresource) { + return true } - if result { - break + if allowEphemeralContainers { + // TODO: we should check if GVK matches v1/Pod + if subresource == "ephemeralcontainers" { + return true + } } } - return result + return false } diff --git a/pkg/utils/match/kind_test.go b/pkg/utils/match/kind_test.go index 0be0b4e29f..1d525cd298 100644 --- a/pkg/utils/match/kind_test.go +++ b/pkg/utils/match/kind_test.go @@ -4,81 +4,52 @@ 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"}, "", false) + match := CheckKind([]string{"*"}, schema.GroupVersionKind{Kind: "Deployment", Group: "", Version: "v1"}, "", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "", false) + match = CheckKind([]string{"Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "", false) + match = CheckKind([]string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"tekton.dev/v1beta1/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1beta1"}, "", false) + match = CheckKind([]string{"tekton.dev/v1beta1/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1beta1"}, "", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"tekton.dev/*/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1alpha1"}, "", false) + match = CheckKind([]string{"tekton.dev/*/TaskRun"}, schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1alpha1"}, "", false) 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", false) + match = CheckKind([]string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status", false) assert.Equal(t, match, false) // Though both 'pods', 'pods/ephemeralcontainers' have same kind i.e. 'Pod' but they are different resources, allowEphemeralContainers governs how to match this case. - match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers", true) + match = CheckKind([]string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers", true) assert.Equal(t, match, true) // Though both 'pods', 'pods/ephemeralcontainers' have same kind i.e. 'Pod' but they are different resources, allowEphemeralContainers governs how to match this case. - match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers", false) + match = CheckKind([]string{"v1/Pod"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "ephemeralcontainers", false) assert.Equal(t, match, false) - match = CheckKind(subresourceGVKToAPIResource, []string{"postgresdb"}, schema.GroupVersionKind{Kind: "postgresdb", Group: "acid.zalan.do", Version: "v1"}, "", false) + match = CheckKind([]string{"postgresdb"}, schema.GroupVersionKind{Kind: "postgresdb", Group: "acid.zalan.do", Version: "v1"}, "", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"Postgresdb"}, schema.GroupVersionKind{Kind: "postgresdb", Group: "acid.zalan.do", Version: "v1"}, "", false) + match = CheckKind([]string{"Postgresdb"}, schema.GroupVersionKind{Kind: "postgresdb", Group: "acid.zalan.do", Version: "v1"}, "", false) 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", false) + match = CheckKind([]string{"networking.k8s.io/v1/NetworkPolicy/status"}, schema.GroupVersionKind{Kind: "NetworkPolicy", Group: "networking.k8s.io", Version: "v1"}, "status", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"v1/Pod.status"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status", false) + match = CheckKind([]string{"v1/Pod.status"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "status", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"*/Pod.eviction"}, schema.GroupVersionKind{Kind: "Eviction", Group: "policy", Version: "v1"}, "eviction", false) + match = CheckKind([]string{"*/Pod.eviction"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "eviction", false) assert.Equal(t, match, true) - match = CheckKind(subresourceGVKToAPIResource, []string{"v1alpha1/Pod.eviction"}, schema.GroupVersionKind{Kind: "Eviction", Group: "policy", Version: "v1"}, "eviction", false) + match = CheckKind([]string{"v1alpha1/Pod.eviction"}, schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"}, "eviction", false) assert.Equal(t, match, false) } diff --git a/pkg/utils/match/match.go b/pkg/utils/match/match.go index 7a350fce8c..f112197a15 100644 --- a/pkg/utils/match/match.go +++ b/pkg/utils/match/match.go @@ -9,8 +9,8 @@ import ( datautils "github.com/kyverno/kyverno/pkg/utils/data" "github.com/kyverno/kyverno/pkg/utils/wildcard" "go.uber.org/multierr" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" ) func CheckNamespace(statement string, resource unstructured.Unstructured) error { @@ -27,10 +27,10 @@ func CheckMatchesResources( resource unstructured.Unstructured, statement kyvernov2beta1.MatchResources, namespaceLabels map[string]string, - subresourceGVKToAPIResource map[string]*metav1.APIResource, - subresourceInAdmnReview string, admissionInfo kyvernov1beta1.RequestInfo, excludeGroupRole []string, + gvk schema.GroupVersionKind, + subresource string, ) error { var errs []error if len(statement.Any) > 0 { @@ -43,10 +43,10 @@ func CheckMatchesResources( rmr, resource, namespaceLabels, - subresourceGVKToAPIResource, - subresourceInAdmnReview, admissionInfo, excludeGroupRole, + gvk, + subresource, )) == 0 { oneMatched = true break @@ -64,10 +64,10 @@ func CheckMatchesResources( rmr, resource, namespaceLabels, - subresourceGVKToAPIResource, - subresourceInAdmnReview, admissionInfo, excludeGroupRole, + gvk, + subresource, )..., ) } @@ -79,10 +79,10 @@ func checkResourceFilter( statement kyvernov1.ResourceFilter, resource unstructured.Unstructured, namespaceLabels map[string]string, - subresourceGVKToAPIResource map[string]*metav1.APIResource, - subresourceInAdmnReview string, admissionInfo kyvernov1beta1.RequestInfo, excludeGroupRole []string, + gvk schema.GroupVersionKind, + subresource string, ) []error { var errs []error // checking if the block is empty @@ -94,8 +94,8 @@ func checkResourceFilter( statement.ResourceDescription, resource, namespaceLabels, - subresourceGVKToAPIResource, - subresourceInAdmnReview, + gvk, + subresource, ) userErrs := checkUserInfo( statement.UserInfo, @@ -138,13 +138,13 @@ func checkResourceDescription( conditionBlock kyvernov1.ResourceDescription, resource unstructured.Unstructured, namespaceLabels map[string]string, - subresourceGVKToAPIResource map[string]*metav1.APIResource, - subresourceInAdmnReview string, + gvk schema.GroupVersionKind, + subresource string, ) []error { var errs []error if len(conditionBlock.Kinds) > 0 { // Matching on ephemeralcontainers even when they are not explicitly specified is only applicable to policies. - if !CheckKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview, false) { + if !CheckKind(conditionBlock.Kinds, gvk, subresource, false) { errs = append(errs, fmt.Errorf("kind does not match %v", conditionBlock.Kinds)) } } diff --git a/pkg/webhooks/utils/policy_context_builder.go b/pkg/webhooks/utils/policy_context_builder.go index 51ad42d0d3..a90c2a685b 100644 --- a/pkg/webhooks/utils/policy_context_builder.go +++ b/pkg/webhooks/utils/policy_context_builder.go @@ -18,6 +18,7 @@ type PolicyContextBuilder interface { type policyContextBuilder struct { configuration config.Configuration + client dclient.Interface rbLister rbacv1listers.RoleBindingLister crbLister rbacv1listers.ClusterRoleBindingLister } @@ -30,6 +31,7 @@ func NewPolicyContextBuilder( ) PolicyContextBuilder { return &policyContextBuilder{ configuration: configuration, + client: client, rbLister: rbLister, crbLister: crbLister, } @@ -45,5 +47,5 @@ func (b *policyContextBuilder) Build(request *admissionv1.AdmissionRequest) (*en userRequestInfo.Roles = roles userRequestInfo.ClusterRoles = clusterRoles } - return engine.NewPolicyContextFromAdmissionRequest(request, userRequestInfo, b.configuration) + return engine.NewPolicyContextFromAdmissionRequest(b.client.Discovery(), request, userRequestInfo, b.configuration) } diff --git a/test/cli/test/exec-subresource/deny-exec-by-pod-label.yaml b/test/cli/test/exec-subresource/deny-exec-by-pod-label.yaml index 98a869e08e..9bbc10cf77 100644 --- a/test/cli/test/exec-subresource/deny-exec-by-pod-label.yaml +++ b/test/cli/test/exec-subresource/deny-exec-by-pod-label.yaml @@ -20,7 +20,7 @@ spec: all: - resources: kinds: - - PodExecOptions + - Pod/exec context: - name: podexeclabel apiCall: diff --git a/test/cli/test/scale-subresource/enforce-replicas-for-scale-subresource.yml b/test/cli/test/scale-subresource/enforce-replicas-for-scale-subresource.yml index efc1978eec..c1c82d6e45 100644 --- a/test/cli/test/scale-subresource/enforce-replicas-for-scale-subresource.yml +++ b/test/cli/test/scale-subresource/enforce-replicas-for-scale-subresource.yml @@ -10,7 +10,7 @@ spec: all: - resources: kinds: - - "Deployment/scale" + - Deployment/scale names: - nginx-test namespaces: