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:
parent
0774398977
commit
aa6400269e
19 changed files with 227 additions and 354 deletions
|
@ -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")
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ spec:
|
|||
all:
|
||||
- resources:
|
||||
kinds:
|
||||
- PodExecOptions
|
||||
- Pod/exec
|
||||
context:
|
||||
- name: podexeclabel
|
||||
apiCall:
|
||||
|
|
|
@ -10,7 +10,7 @@ spec:
|
|||
all:
|
||||
- resources:
|
||||
kinds:
|
||||
- "Deployment/scale"
|
||||
- Deployment/scale
|
||||
names:
|
||||
- nginx-test
|
||||
namespaces:
|
||||
|
|
Loading…
Add table
Reference in a new issue