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

refactor: implement matching based on GVK/subresource (#6633)

* refactor: do not allow matching with subresource kind

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

* fix kuttl

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

* fix kuttl

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

* fixes

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>

* refactor: implement matching based on GVK/subresource

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>

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

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-03-22 11:18:11 +01:00 committed by GitHub
parent 0774398977
commit aa6400269e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 227 additions and 354 deletions

View file

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

View file

@ -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(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ spec:
all:
- resources:
kinds:
- PodExecOptions
- Pod/exec
context:
- name: podexeclabel
apiCall:

View file

@ -10,7 +10,7 @@ spec:
all:
- resources:
kinds:
- "Deployment/scale"
- Deployment/scale
names:
- nginx-test
namespaces: