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:
parent
cea7a7e11e
commit
bc07943c81
15 changed files with 216 additions and 251 deletions
|
@ -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
|
||||
}
|
||||
|
|
22
pkg/dclient/discovery_test.go
Normal file
22
pkg/dclient/discovery_test.go
Normal 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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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])
|
||||
}
|
||||
|
|
65
pkg/utils/kube/kind_test.go
Normal file
65
pkg/utils/kube/kind_test.go
Normal 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, "")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Add table
Reference in a new issue