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

handle subresources (#3841)

* handle subresources

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* make fmt

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix logger name

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix webhook and logs

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* make fmt

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Jim Bugwadia 2022-05-09 18:50:50 -07:00 committed by GitHub
parent cea7a7e11e
commit bc07943c81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 216 additions and 251 deletions

View file

@ -5,6 +5,8 @@ import (
"strings"
"time"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
openapiv2 "github.com/googleapis/gnostic/openapiv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -126,23 +128,22 @@ func (c serverPreferredResources) findResource(apiVersion string, kind string) (
}
}
k, subresource := kubeutils.SplitSubresource(kind)
if subresource != "" {
kind = k
}
for _, serverResource := range serverResources {
if apiVersion != "" && serverResource.GroupVersion != apiVersion {
continue
}
for _, resource := range serverResource.APIResources {
if strings.Contains(resource.Name, "/") {
// skip the sub-resources like deployment/status
continue
}
// match kind or names (e.g. Namespace, namespaces, namespace)
// to allow matching API paths (e.g. /api/v1/namespaces).
if resource.Kind == kind || resource.Name == kind || resource.SingularName == kind {
if resourceMatches(resource, kind, subresource) {
logger.V(4).Info("matched API resource to kind", "apiResource", resource, "kind", kind)
gv, err := schema.ParseGroupVersion(serverResource.GroupVersion)
if err != nil {
logger.Error(err, "failed to parse groupVersion", "groupVersion", serverResource.GroupVersion)
logger.Error(err, "failed to parse GV", "groupVersion", serverResource.GroupVersion)
return nil, schema.GroupVersionResource{}, err
}
@ -153,3 +154,14 @@ func (c serverPreferredResources) findResource(apiVersion string, kind string) (
return nil, schema.GroupVersionResource{}, fmt.Errorf("kind '%s' not found in apiVersion '%s'", kind, apiVersion)
}
// resourceMatches checks the resource Kind, Name, SingularName and a subresource if specified
// e.g. &apiResource{Name: "taskruns/status", Kind: "TaskRun"} will match "kind=TaskRun, subresource=Status"
func resourceMatches(resource metav1.APIResource, kind, subresource string) bool {
if resource.Kind == kind || resource.Name == kind || resource.SingularName == kind {
_, s := kubeutils.SplitSubresource(resource.Name)
return strings.EqualFold(s, subresource)
}
return false
}

View file

@ -0,0 +1,22 @@
package client
import (
"testing"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Test_resourceMatches(t *testing.T) {
ar := metav1.APIResource{Name: "taskruns/status", Kind: "TaskRun"}
assert.Equal(t, resourceMatches(ar, "TaskRun", "Status"), true)
ar = metav1.APIResource{Name: "taskruns/status", Kind: "TaskRun"}
assert.Equal(t, resourceMatches(ar, "TaskRun", ""), false)
ar = metav1.APIResource{Name: "taskruns", Kind: "TaskRun"}
assert.Equal(t, resourceMatches(ar, "TaskRun", ""), true)
ar = metav1.APIResource{Name: "tasks/status", Kind: "Task"}
assert.Equal(t, resourceMatches(ar, "TaskRun", "Status"), false)
}

View file

@ -273,7 +273,7 @@ func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCont
return fmt.Errorf("failed to add JMESPath (%s) results to context, error: %v", entry.APICall.JMESPath, err)
}
logger.Info("added APICall context entry", "data", contextData)
logger.V(4).Info("added APICall context entry", "len", len(contextData))
return nil
}

View file

@ -6,6 +6,8 @@ import (
"strings"
"time"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/go-logr/logr"
wildcard "github.com/kyverno/go-wildcard"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
@ -34,19 +36,25 @@ type EngineStats struct {
RulesAppliedCount int
}
func checkKind(kinds []string, resource unstructured.Unstructured) bool {
for _, kind := range kinds {
SplitGVK := strings.Split(kind, "/")
if len(SplitGVK) == 1 {
if resource.GetKind() == strings.Title(kind) || kind == "*" {
func checkKind(kinds []string, resourceKind string, gvk schema.GroupVersionKind) bool {
for _, k := range kinds {
parts := strings.Split(k, "/")
if len(parts) == 1 {
if k == "*" || resourceKind == strings.Title(k) {
return true
}
} else if len(SplitGVK) == 2 {
if resource.GroupVersionKind().Kind == strings.Title(SplitGVK[1]) && resource.GroupVersionKind().Version == SplitGVK[0] {
}
if len(parts) == 2 {
kindParts := strings.SplitN(parts[1], ".", 2)
if gvk.Kind == strings.Title(kindParts[0]) && gvk.Version == parts[0] {
return true
}
} else {
if resource.GroupVersionKind().Group == SplitGVK[0] && resource.GroupVersionKind().Kind == strings.Title(SplitGVK[2]) && (resource.GroupVersionKind().Version == SplitGVK[1] || SplitGVK[1] == "*") {
}
if len(parts) == 3 || len(parts) == 4 {
kindParts := strings.SplitN(parts[2], ".", 2)
if gvk.Group == parts[0] && (gvk.Version == parts[1] || parts[1] == "*") && gvk.Kind == strings.Title(kindParts[0]) {
return true
}
}
@ -130,7 +138,7 @@ func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription,
var errs []error
if len(conditionBlock.Kinds) > 0 {
if !checkKind(conditionBlock.Kinds, resource) {
if !checkKind(conditionBlock.Kinds, resource.GetKind(), resource.GroupVersionKind()) {
errs = append(errs, fmt.Errorf("kind does not match %v", conditionBlock.Kinds))
}
}
@ -272,6 +280,7 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
if policyNamespace != "" && policyNamespace != resourceRef.GetNamespace() {
return errors.New(" The policy and resource namespace are different. Therefore, policy skip this resource.")
}
if len(rule.MatchResources.Any) > 0 {
// include object if ANY of the criteria match
// so if one matches then break from loop

View file

@ -4,6 +4,8 @@ import (
"encoding/json"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
v1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/autogen"
@ -1453,3 +1455,23 @@ func TestManagedPodResource(t *testing.T) {
assert.Equal(t, res, tc.expectedResult, "test %d/%s failed, expect %v, got %v", i+1, tc.name, tc.expectedResult, res)
}
}
func Test_checkKind(t *testing.T) {
match := checkKind([]string{"*"}, "Deployment", schema.GroupVersionKind{Kind: "Deployment", Group: "", Version: "v1"})
assert.Equal(t, match, true)
match = checkKind([]string{"Pod"}, "Pod", schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"})
assert.Equal(t, match, true)
match = checkKind([]string{"v1/Pod"}, "Pod", schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"})
assert.Equal(t, match, true)
match = checkKind([]string{"tekton.dev/v1beta1/TaskRun"}, "TaskRun", schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1beta1"})
assert.Equal(t, match, true)
match = checkKind([]string{"tekton.dev/v1beta1/TaskRun/status"}, "TaskRun", schema.GroupVersionKind{Kind: "TaskRun", Group: "tekton.dev", Version: "v1beta1"})
assert.Equal(t, match, true)
match = checkKind([]string{"v1/pod.status"}, "Pod", schema.GroupVersionKind{Kind: "Pod", Group: "", Version: "v1"})
assert.Equal(t, match, true)
}

View file

@ -14,7 +14,6 @@ import (
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/utils"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
cmap "github.com/orcaman/concurrent-map"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
@ -141,7 +140,7 @@ func (o *Controller) ValidatePolicyMutation(policy v1.PolicyInterface) error {
for _, rule := range autogen.ComputeRules(policy) {
if rule.HasMutate() {
for _, kind := range rule.MatchResources.Kinds {
kindToRules[kind] = append(kindToRules[kubeutils.GetFormatedKind(kind)], rule)
kindToRules[kind] = append(kindToRules[kind], rule)
}
}
}

View file

@ -1040,7 +1040,7 @@ func validateKinds(kinds []string, mock bool, client dclient.Interface, p kyvern
if !mock && !kubeutils.SkipSubResources(k) && !strings.Contains(kind, "*") {
_, _, err := client.Discovery().FindResource(gv, k)
if err != nil {
return fmt.Errorf("unable to convert GVK to GVR, %s, err: %s", kinds, err)
return fmt.Errorf("unable to convert GVK to GVR for kinds %s, err: %s", kinds, err)
}
}
}

View file

@ -1,7 +1,6 @@
package policycache
import (
"strings"
"sync"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
@ -116,7 +115,7 @@ func (m *pMap) update(old kyverno.PolicyInterface, new kyverno.PolicyInterface)
func addCacheHelper(rmr kyverno.ResourceFilter, m *pMap, rule kyverno.Rule, pName string, enforcePolicy bool) {
for _, gvk := range rmr.Kinds {
_, k := kubeutils.GetKindFromGVK(gvk)
kind := strings.Title(k)
kind, _ := kubeutils.SplitSubresource(k)
_, ok := m.kindDataMap[kind]
if !ok {
m.kindDataMap[kind] = make(map[PolicyType][]string)

View file

@ -10,7 +10,6 @@ import (
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/toggle"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
)
// GenerateJSONPatchesForDefaults generates default JSON patches for
@ -48,93 +47,10 @@ func GenerateJSONPatchesForDefaults(policy kyverno.PolicyInterface, log logr.Log
}
patches = append(patches, patch...)
}
formatedGVK, errs := checkForGVKFormatPatch(policy, log)
if len(errs) > 0 {
var errMsgs []string
for _, err := range errs {
errMsgs = append(errMsgs, err.Error())
log.Error(err, "failed to format the kind")
}
updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";"))
}
patches = append(patches, formatedGVK...)
return jsonutils.JoinPatches(patches...), updateMsgs
}
func checkForGVKFormatPatch(policy kyverno.PolicyInterface, log logr.Logger) (patches [][]byte, errs []error) {
patches = make([][]byte, 0)
for i, rule := range autogen.ComputeRules(policy) {
patchByte, err := convertGVKForKinds(fmt.Sprintf("/spec/rules/%s/match/resources/kinds", strconv.Itoa(i)), rule.MatchResources.Kinds, log)
if err == nil && patchByte != nil {
patches = append(patches, patchByte)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to GVK for rule '%s/%s/%d/match': %v", policy.GetName(), rule.Name, i, err))
}
for j, matchAll := range rule.MatchResources.All {
patchByte, err := convertGVKForKinds(fmt.Sprintf("/spec/rules/%s/match/all/%s/resources/kinds", strconv.Itoa(i), strconv.Itoa(j)), matchAll.ResourceDescription.Kinds, log)
if err == nil && patchByte != nil {
patches = append(patches, patchByte)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to convert GVK for rule '%s/%s/%d/match/all/%d': %v", policy.GetName(), rule.Name, i, j, err))
}
}
for k, matchAny := range rule.MatchResources.Any {
patchByte, err := convertGVKForKinds(fmt.Sprintf("/spec/rules/%s/match/any/%s/resources/kinds", strconv.Itoa(i), strconv.Itoa(k)), matchAny.ResourceDescription.Kinds, log)
if err == nil && patchByte != nil {
patches = append(patches, patchByte)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to convert GVK for rule '%s/%s/%d/match/any/%d': %v", policy.GetName(), rule.Name, i, k, err))
}
}
patchByte, err = convertGVKForKinds(fmt.Sprintf("/spec/rules/%s/exclude/resources/kinds", strconv.Itoa(i)), rule.ExcludeResources.Kinds, log)
if err == nil && patchByte != nil {
patches = append(patches, patchByte)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to convert GVK for rule '%s/%s/%d/exclude': %v", policy.GetName(), rule.Name, i, err))
}
for j, excludeAll := range rule.ExcludeResources.All {
patchByte, err := convertGVKForKinds(fmt.Sprintf("/spec/rules/%s/exclude/all/%s/resources/kinds", strconv.Itoa(i), strconv.Itoa(j)), excludeAll.ResourceDescription.Kinds, log)
if err == nil && patchByte != nil {
patches = append(patches, patchByte)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to convert GVK for rule '%s/%s/%d/exclude/all/%d': %v", policy.GetName(), rule.Name, i, j, err))
}
}
for k, excludeAny := range rule.ExcludeResources.Any {
patchByte, err := convertGVKForKinds(fmt.Sprintf("/spec/rules/%s/exclude/any/%s/resources/kinds", strconv.Itoa(i), strconv.Itoa(k)), excludeAny.ResourceDescription.Kinds, log)
if err == nil && patchByte != nil {
patches = append(patches, patchByte)
} else if err != nil {
errs = append(errs, fmt.Errorf("failed to convert GVK for rule '%s/%s/%d/exclude/any/%d': %v", policy.GetName(), rule.Name, i, k, err))
}
}
}
return patches, errs
}
func convertGVKForKinds(path string, kinds []string, log logr.Logger) ([]byte, error) {
kindList := []string{}
for _, k := range kinds {
gvk := kubeutils.GetFormatedKind(k)
if gvk == k {
continue
}
kindList = append(kindList, gvk)
}
if len(kindList) == 0 {
return nil, nil
}
p, err := jsonutils.MarshalPatch(path, "replace", kindList)
log.V(4).WithName("convertGVKForKinds").Info("generated patch", "patch", string(p))
return p, err
}
func defaultBackgroundFlag(spec *kyverno.Spec, log logr.Logger) ([]byte, string) {
// set 'Background' flag to 'true' if not specified
if spec.Background == nil {

View file

@ -1,103 +0,0 @@
package policymutation
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"gotest.tools/assert"
"sigs.k8s.io/controller-runtime/pkg/log"
)
func currentDir() (string, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return "", nil
}
return filepath.Join(homedir, "github.com/kyverno/kyverno"), nil
}
func Test_checkForGVKFormatPatch(t *testing.T) {
testCases := []struct {
name string
policy []byte
expectedPatches []byte
}{
{
name: "match-kinds-empty",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"match-kinds-empty"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["ConfigMap","batch.volcano.sh/v1alpha1/Job"]}},"validate":{"message":"Metadatalabel'name'isrequired.","pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: nil,
},
{
name: "match-kinds",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"match-kinds"},"spec":{"rules":[{"name":"test","match":{"resources":{"kinds":["configmap","batch.volcano.sh/v1alpha1/job"]}},"validate":{"message":"Metadatalabel'name'isrequired.","pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: []byte(`{"path":"/spec/rules/0/match/resources/kinds","op":"replace","value":["Configmap","batch.volcano.sh/v1alpha1/Job"]}`),
},
{
name: "exclude-kinds-empty",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"exclude-kinds-empty"},"spec":{"rules":[{"name":"test","exclude":{"resources":{"kinds":["ConfigMap","batch.volcano.sh/v1alpha1/Job"]}},"validate":{"message":"Metadatalabel'name'isrequired.","pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: nil,
},
{
name: "exclude-kinds",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"exclude-kinds"},"spec":{"rules":[{"name":"test","exclude":{"resources":{"kinds":["configmap","batch.volcano.sh/v1alpha1/job"]}},"validate":{"message":"Metadatalabel'name'isrequired.","pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: []byte(`{"path":"/spec/rules/0/exclude/resources/kinds","op":"replace","value":["Configmap","batch.volcano.sh/v1alpha1/Job"]}`),
},
{
name: "match-any-kinds-empty",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"match-any-kinds-empty"},"spec":{"rules":[{"name":"test","match":{"any":[{"resources":{"kinds":["Deployment","Pod","DaemonSet"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: nil,
},
{
name: "match-any-kinds",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"match-any-kinds"},"spec":{"rules":[{"name":"test","match":{"any":[{"resources":{"kinds":["deployment","pod","DaemonSet"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: []byte(`{"path":"/spec/rules/0/match/any/0/resources/kinds","op":"replace","value":["Deployment","Pod"]}`),
},
{
name: "match-all-kinds-empty",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"match-all-kinds-empty"},"spec":{"rules":[{"name":"test","match":{"all":[{"resources":{"kinds":["Deployment","Pod","DaemonSet"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: nil,
},
{
name: "match-all-kinds",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"match-all-kinds"},"spec":{"rules":[{"name":"test","match":{"all":[{"resources":{"kinds":["deployment","pod","DaemonSet"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: []byte(`{"path":"/spec/rules/0/match/all/0/resources/kinds","op":"replace","value":["Deployment","Pod"]}`),
},
{
name: "exclude-any-kinds-empty",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"exclude-any-kinds-empty"},"spec":{"rules":[{"name":"test","exclude":{"any":[{"resources":{"kinds":["Deployment","Pod","DaemonSet"]}},{"resources":{"kinds":["ConfigMap"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: nil,
},
{
name: "exclude-any-kinds",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"exclude-any-kinds-empty"},"spec":{"rules":[{"name":"test","exclude":{"any":[{"resources":{"kinds":["Deployment","Pod","DaemonSet"]}},{"resources":{"kinds":["configMap"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: []byte(`{"path":"/spec/rules/0/exclude/any/1/resources/kinds","op":"replace","value":["ConfigMap"]}`),
},
{
name: "exclude-all-kinds-empty",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"exclude-all-kinds-empty"},"spec":{"rules":[{"name":"test","exclude":{"all":[{"resources":{"kinds":["Deployment","Pod","DaemonSet"]}},{"resources":{"kinds":["ConfigMap"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: nil,
},
{
name: "exclude-all-kinds",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"exclude-all-kinds"},"spec":{"rules":[{"name":"test","exclude":{"all":[{"resources":{"kinds":["Deployment","pod","DaemonSet"]}},{"resources":{"kinds":["ConfigMap"]}}]},"validate":{"pattern":{"metadata":{"labels":{"name":"?*"}}}}}]}}`),
expectedPatches: []byte(`{"path":"/spec/rules/0/exclude/all/0/resources/kinds","op":"replace","value":["Pod"]}`),
},
}
for _, test := range testCases {
var policy kyverno.ClusterPolicy
err := json.Unmarshal(test.policy, &policy)
assert.NilError(t, err, fmt.Sprintf("failed to convert policy test %s: %v", test.name, err))
patches, errs := checkForGVKFormatPatch(&policy, log.Log)
assert.Assert(t, len(errs) == 0)
for _, p := range patches {
assert.Equal(t, string(p), string(test.expectedPatches), fmt.Sprintf("test %s failed", test.name))
}
}
}

View file

@ -4,17 +4,39 @@ import "strings"
// GetKindFromGVK - get kind and APIVersion from GVK
func GetKindFromGVK(str string) (apiVersion string, kind string) {
if strings.Count(str, "/") == 0 {
return "", str
parts := strings.Split(str, "/")
count := len(parts)
if count == 2 {
return parts[0], formatSubresource(parts[1])
}
splitString := strings.Split(str, "/")
if strings.Count(str, "/") == 1 {
return splitString[0], splitString[1]
if count == 3 {
if parts[1] == "*" {
return "", formatSubresource(parts[2])
}
return parts[0] + "/" + parts[1], formatSubresource(parts[2])
}
if splitString[1] == "*" {
return "", splitString[2]
if count == 4 {
return parts[0] + "/" + parts[1], parts[2] + "/" + parts[3]
}
return splitString[0] + "/" + splitString[1], splitString[2]
return "", formatSubresource(str)
}
func formatSubresource(s string) string {
return strings.Replace(s, ".", "/", 1)
}
func SplitSubresource(s string) (kind string, subresource string) {
normalized := strings.Replace(s, ".", "/", 1)
parts := strings.Split(normalized, "/")
if len(parts) == 2 {
return parts[0], parts[1]
}
return s, ""
}
func ContainsKind(list []string, kind string) bool {
@ -26,19 +48,8 @@ func ContainsKind(list []string, kind string) bool {
return false
}
// SkipSubResources check to skip list of resources which don't have group.
// SkipSubResources skip list of resources which don't have an API group.
func SkipSubResources(kind string) bool {
s := []string{"PodExecOptions", "PodAttachOptions", "PodProxyOptions", "ServiceProxyOptions", "NodeProxyOptions"}
return ContainsKind(s, kind)
}
func GetFormatedKind(str string) (kind string) {
if strings.Count(str, "/") == 0 {
return strings.Title(str)
}
splitString := strings.Split(str, "/")
if strings.Count(str, "/") == 1 {
return splitString[0] + "/" + strings.Title(splitString[1])
}
return splitString[0] + "/" + splitString[1] + "/" + strings.Title(splitString[2])
}

View file

@ -0,0 +1,65 @@
package kube
import (
"testing"
"gotest.tools/assert"
)
func Test_GetKindFromGVK(t *testing.T) {
var apiVersion, kind string
apiVersion, kind = GetKindFromGVK("*")
assert.Equal(t, "", apiVersion)
assert.Equal(t, "*", kind)
apiVersion, kind = GetKindFromGVK("Pod")
assert.Equal(t, "", apiVersion)
assert.Equal(t, "Pod", kind)
apiVersion, kind = GetKindFromGVK("v1/Pod")
assert.Equal(t, "v1", apiVersion)
assert.Equal(t, "Pod", kind)
apiVersion, kind = GetKindFromGVK("batch/*/CronJob")
assert.Equal(t, "", apiVersion)
assert.Equal(t, "CronJob", kind)
apiVersion, kind = GetKindFromGVK("storage.k8s.io/v1/CSIDriver")
assert.Equal(t, "storage.k8s.io/v1", apiVersion)
assert.Equal(t, "CSIDriver", kind)
apiVersion, kind = GetKindFromGVK("tekton.dev/v1beta1/TaskRun/Status")
assert.Equal(t, "tekton.dev/v1beta1", apiVersion)
assert.Equal(t, "TaskRun/Status", kind)
apiVersion, kind = GetKindFromGVK("v1/Pod.Status")
assert.Equal(t, "v1", apiVersion)
assert.Equal(t, "Pod/Status", kind)
apiVersion, kind = GetKindFromGVK("Pod.Status")
assert.Equal(t, "", apiVersion)
assert.Equal(t, "Pod/Status", kind)
}
func Test_SplitSubresource(t *testing.T) {
var kind, subresource string
kind, subresource = SplitSubresource("TaskRun/Status")
assert.Equal(t, kind, "TaskRun")
assert.Equal(t, subresource, "Status")
kind, subresource = SplitSubresource("TaskRun/status")
assert.Equal(t, kind, "TaskRun")
assert.Equal(t, subresource, "status")
kind, subresource = SplitSubresource("Pod.Status")
assert.Equal(t, kind, "Pod")
assert.Equal(t, subresource, "Status")
kind, subresource = SplitSubresource("v1/Pod/Status")
assert.Equal(t, kind, "v1/Pod/Status")
assert.Equal(t, subresource, "")
kind, subresource = SplitSubresource("v1/Pod.Status")
assert.Equal(t, kind, "v1/Pod.Status")
assert.Equal(t, subresource, "")
}

View file

@ -638,6 +638,7 @@ func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy kyverno.PolicyI
if strings.Contains(gvk, "*") {
gvrList = append(gvrList, schema.GroupVersionResource{Group: gvr.Group, Version: "*", Resource: gvr.Resource})
} else {
m.log.V(4).Info("configuring webhook", "GVK", gvk, "GVR", gvr)
gvrList = append(gvrList, gvr)
}
}

View file

@ -43,7 +43,7 @@ func errorResponse(logger logr.Logger, err error, message string) *admissionv1.A
}
func setupLogger(logger logr.Logger, name string, request *admissionv1.AdmissionRequest) logr.Logger {
return logger.WithName("MutateWebhook").WithValues(
return logger.WithName(name).WithValues(
"uid", request.UID,
"kind", request.Kind,
"namespace", request.Namespace,
@ -61,8 +61,7 @@ func (ws *WebhookServer) admissionHandler(filter bool, inner handlers.AdmissionH
}
func (ws *WebhookServer) policyMutation(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
logger := setupLogger(ws.log, "policy mutation", request)
logger := setupLogger(ws.log, "PolicyMutationWebhook", request)
policy, oldPolicy, err := admissionutils.GetPolicies(request)
if err != nil {
logger.Error(err, "failed to unmarshal policies from admission request")
@ -88,7 +87,7 @@ func (ws *WebhookServer) policyMutation(request *admissionv1.AdmissionRequest) *
//policyValidation performs the validation check on policy resource
func (ws *WebhookServer) policyValidation(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
logger := setupLogger(ws.log, "policy validation", request)
logger := setupLogger(ws.log, "PolicyValidationWebhook", request)
policy, oldPolicy, err := admissionutils.GetPolicies(request)
if err != nil {
logger.Error(err, "failed to unmarshal policies from admission request")
@ -119,7 +118,7 @@ func (ws *WebhookServer) policyValidation(request *admissionv1.AdmissionRequest)
// resourceMutation mutates resource
func (ws *WebhookServer) resourceMutation(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
logger := setupLogger(ws.log, "resource mutation", request)
logger := setupLogger(ws.log, "ResourceMutationWebhook", request)
if excludeKyvernoResources(request.Kind.Kind) {
return admissionutils.ResponseSuccess(true, "")
}
@ -131,21 +130,25 @@ func (ws *WebhookServer) resourceMutation(request *admissionv1.AdmissionRequest)
} else {
logger.Info(fmt.Sprintf("Converting oldObject failed: %v", err))
}
return admissionutils.ResponseSuccess(true, "")
return admissionutils.ResponseSuccess(true, "")
}
logger.V(4).Info("received an admission request in mutating webhook")
kind := request.Kind.Kind
logger.V(4).Info("received an admission request in mutating webhook", "kind", kind)
requestTime := time.Now().Unix()
mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, request.Kind.Kind, request.Namespace)
verifyImagesPolicies := ws.pCache.GetPolicies(policycache.VerifyImagesMutate, request.Kind.Kind, request.Namespace)
mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)
verifyImagesPolicies := ws.pCache.GetPolicies(policycache.VerifyImagesMutate, kind, request.Namespace)
if len(mutatePolicies) == 0 && len(verifyImagesPolicies) == 0 {
logger.V(4).Info("no policies matched admission request")
logger.V(4).Info("no policies matched mutate admission request", "kind", kind)
return admissionutils.ResponseSuccess(true, "")
}
logger.V(4).Info("processing policies for mutate admission request", "kind", kind,
"mutatePolicies", len(mutatePolicies), "verifyImagesPolicies", len(verifyImagesPolicies))
addRoles := containsRBACInfo(mutatePolicies)
policyContext, err := ws.buildPolicyContext(request, addRoles)
if err != nil {
@ -172,7 +175,7 @@ func (ws *WebhookServer) resourceMutation(request *admissionv1.AdmissionRequest)
}
func (ws *WebhookServer) resourceValidation(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
logger := setupLogger(ws.log, "resource validation", request)
logger := setupLogger(ws.log, "ResourceValidationWebhook", request)
if request.Operation == admissionv1.Delete {
ws.handleDelete(request)
}
@ -181,22 +184,29 @@ func (ws *WebhookServer) resourceValidation(request *admissionv1.AdmissionReques
return admissionutils.ResponseSuccess(true, "")
}
logger.V(6).Info("received an admission request in validating webhook")
kind := request.Kind.Kind
logger.V(4).Info("received an admission request in validating webhook", "kind", kind)
// timestamp at which this admission request got triggered
requestTime := time.Now().Unix()
policies := ws.pCache.GetPolicies(policycache.ValidateEnforce, request.Kind.Kind, request.Namespace)
mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, request.Kind.Kind, request.Namespace)
generatePolicies := ws.pCache.GetPolicies(policycache.Generate, request.Kind.Kind, request.Namespace)
imageVerifyValidatePolicies := ws.pCache.GetPolicies(policycache.VerifyImagesValidate, request.Kind.Kind, request.Namespace)
policies := ws.pCache.GetPolicies(policycache.ValidateEnforce, kind, request.Namespace)
mutatePolicies := ws.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)
generatePolicies := ws.pCache.GetPolicies(policycache.Generate, kind, request.Namespace)
imageVerifyValidatePolicies := ws.pCache.GetPolicies(policycache.VerifyImagesValidate, kind, request.Namespace)
policies = append(policies, imageVerifyValidatePolicies...)
if len(policies) == 0 && len(mutatePolicies) == 0 && len(generatePolicies) == 0 {
logger.V(4).Info("no policies matched admission request", "kind", kind)
}
if len(generatePolicies) == 0 && request.Operation == admissionv1.Update {
// handle generate source resource updates
go ws.handleUpdatesForGenerateRules(request, []kyverno.PolicyInterface{})
}
logger.V(4).Info("processing policies for validate admission request",
"kind", kind, "validate", len(policies), "mutate", len(mutatePolicies), "generate", len(generatePolicies))
var roles, clusterRoles []string
if containsRBACInfo(policies, generatePolicies) {
var err error

View file

@ -420,7 +420,9 @@ func Test_Mutate_Existing(t *testing.T) {
Expect(err).NotTo(HaveOccurred())
// wait for UR to be completed
time.Sleep(3 * time.Second)
// TODO: this should be changed to check the UR for the right state.
// Any hard-coded timer may fail in some cases.
time.Sleep(5 * time.Second)
res, err := e2eClient.GetNamespacedResource(test.TargetGVR, test.TargetNamespace, test.TargetName)
Expect(err).NotTo(HaveOccurred())