diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 66b5e588f3..167188cf69 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -14,19 +14,15 @@ import ( "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/variables" - "github.com/kyverno/kyverno/pkg/engine/wildcards" - "github.com/kyverno/kyverno/pkg/logging" datautils "github.com/kyverno/kyverno/pkg/utils/data" - matched "github.com/kyverno/kyverno/pkg/utils/match" + matchutils "github.com/kyverno/kyverno/pkg/utils/match" "github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/pkg/errors" - "golang.org/x/exp/slices" authenticationv1 "k8s.io/api/authentication/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" ) // EngineStats stores in the statistics for a single application of resource @@ -37,10 +33,6 @@ type EngineStats struct { RulesAppliedCount int } -func checkName(name, resourceName string) bool { - return wildcard.Match(name, resourceName) -} - func checkNameSpace(namespaces []string, resource unstructured.Unstructured) bool { resourceNameSpace := resource.GetNamespace() if resource.GetKind() == "Namespace" { @@ -56,43 +48,6 @@ func checkNameSpace(namespaces []string, resource unstructured.Unstructured) boo return false } -func checkAnnotations(annotations map[string]string, resourceAnnotations map[string]string) bool { - if len(annotations) == 0 { - return true - } - - for k, v := range annotations { - match := false - for k1, v1 := range resourceAnnotations { - if wildcard.Match(k, k1) && wildcard.Match(v, v1) { - match = true - break - } - } - - if !match { - return false - } - } - - return true -} - -func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[string]string) (bool, error) { - wildcards.ReplaceInSelector(labelSelector, resourceLabels) - selector, err := metav1.LabelSelectorAsSelector(labelSelector) - if err != nil { - logging.Error(err, "failed to build label selector") - return false, err - } - - if selector.Matches(labels.Set(resourceLabels)) { - return true, nil - } - - return false, nil -} - // doesResourceMatchConditionBlock filters the resource with defined conditions // for a match / exclude block, it has the following attributes: // ResourceDescription: @@ -117,7 +72,7 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met if len(conditionBlock.Kinds) > 0 { // Matching on ephemeralcontainers even when they are not explicitly specified for backward compatibility. - if !matched.CheckKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview, true) { + if !matchutils.CheckKind(subresourceGVKToAPIResource, conditionBlock.Kinds, resource.GroupVersionKind(), subresourceInAdmnReview, true) { errs = append(errs, fmt.Errorf("kind does not match %v", conditionBlock.Kinds)) } } @@ -128,7 +83,7 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met } if conditionBlock.Name != "" { - if !checkName(conditionBlock.Name, resourceName) { + if !matchutils.CheckName(conditionBlock.Name, resourceName) { errs = append(errs, fmt.Errorf("name does not match")) } } @@ -136,7 +91,7 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met if len(conditionBlock.Names) > 0 { noneMatch := true for i := range conditionBlock.Names { - if checkName(conditionBlock.Names[i], resourceName) { + if matchutils.CheckName(conditionBlock.Names[i], resourceName) { noneMatch = false break } @@ -153,13 +108,13 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met } if len(conditionBlock.Annotations) > 0 { - if !checkAnnotations(conditionBlock.Annotations, resource.GetAnnotations()) { + if !matchutils.CheckAnnotations(conditionBlock.Annotations, resource.GetAnnotations()) { errs = append(errs, fmt.Errorf("annotations does not match")) } } if conditionBlock.Selector != nil { - hasPassed, err := checkSelector(conditionBlock.Selector, resource.GetLabels()) + hasPassed, err := matchutils.CheckSelector(conditionBlock.Selector, resource.GetLabels()) if err != nil { errs = append(errs, fmt.Errorf("failed to parse selector: %v", err)) } else { @@ -170,7 +125,7 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met } if conditionBlock.NamespaceSelector != nil && resource.GetKind() != "Namespace" && resource.GetKind() != "" { - hasPassed, err := checkSelector(conditionBlock.NamespaceSelector, namespaceLabels) + hasPassed, err := matchutils.CheckSelector(conditionBlock.NamespaceSelector, namespaceLabels) if err != nil { errs = append(errs, fmt.Errorf("failed to parse namespace selector: %v", err)) } else { @@ -204,8 +159,6 @@ func doesResourceMatchConditionBlock(subresourceGVKToAPIResource map[string]*met // matchSubjects return true if one of ruleSubjects exist in userInfo func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.UserInfo, dynamicConfig []string) bool { - const SaPrefix = "system:serviceaccount:" - if store.GetMock() { mockSubject := store.GetSubjects().Subject for _, subject := range ruleSubjects { @@ -220,35 +173,9 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User } } } - return false } else { - userGroups := append(userInfo.Groups, userInfo.Username) - // TODO: see issue https://github.com/kyverno/kyverno/issues/861 - for _, e := range dynamicConfig { - ruleSubjects = append(ruleSubjects, - rbacv1.Subject{Kind: "Group", Name: e}, - ) - } - - for _, subject := range ruleSubjects { - switch subject.Kind { - case "ServiceAccount": - if len(userInfo.Username) <= len(SaPrefix) { - continue - } - subjectServiceAccount := subject.Namespace + ":" + subject.Name - if userInfo.Username[len(SaPrefix):] == subjectServiceAccount { - return true - } - case "User", "Group": - if slices.Contains(userGroups, subject.Name) { - return true - } - } - } - - return false + return matchutils.CheckSubjects(ruleSubjects, userInfo, dynamicConfig) } } diff --git a/pkg/engine/utils_test.go b/pkg/engine/utils_test.go index fda3efa1fc..11bf1ca708 100644 --- a/pkg/engine/utils_test.go +++ b/pkg/engine/utils_test.go @@ -2330,76 +2330,6 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) { } } -func TestWildCardLabels(t *testing.T) { - testSelector(t, &metav1.LabelSelector{}, map[string]string{}, true) - - testSelector(t, &metav1.LabelSelector{}, map[string]string{"foo": "bar"}, true) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"test.io/*": "bar"}}, - map[string]string{"foo": "bar"}, false) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"scale.test.io/*": "bar"}}, - map[string]string{"foo": "bar"}, false) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"test.io/*": "bar"}}, - map[string]string{"test.io/scale": "foo", "test.io/functional": "bar"}, true) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"test.io/*": "*"}}, - map[string]string{"test.io/scale": "foo", "test.io/functional": "bar"}, true) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"test.io/*": "a*"}}, - map[string]string{"test.io/scale": "foo", "test.io/functional": "bar"}, false) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"test.io/scale": "f??"}}, - map[string]string{"test.io/scale": "foo", "test.io/functional": "bar"}, true) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"*": "*"}}, - map[string]string{"test.io/scale": "foo", "test.io/functional": "bar"}, true) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"test.io/functional": "foo"}}, - map[string]string{"test.io/scale": "foo", "test.io/functional": "bar"}, false) - - testSelector(t, &metav1.LabelSelector{MatchLabels: map[string]string{"*": "*"}}, - map[string]string{}, false) -} - -func testSelector(t *testing.T, s *metav1.LabelSelector, l map[string]string, match bool) { - res, err := checkSelector(s, l) - if err != nil { - t.Errorf("selector %v failed to select labels %v: %v", s.MatchLabels, l, err) - return - } - - if res != match { - t.Errorf("select %v -> labels %v: expected %v received %v", s.MatchLabels, l, match, res) - } -} - -func TestWildCardAnnotation(t *testing.T) { - // test single annotation values - testAnnotationMatch(t, map[string]string{}, map[string]string{}, true) - testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{}, false) - testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{"tes1/test": "*"}, false) - testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{"test/test": "*"}, true) - testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{"test/bar": "foo"}, true) - testAnnotationMatch(t, map[string]string{"test/b*": "*"}, map[string]string{"test/bar": "foo"}, true) - - // test multiple annotation values - testAnnotationMatch(t, map[string]string{"test/b*": "*", "test2/*": "*"}, - map[string]string{"test/bar": "foo"}, false) - testAnnotationMatch(t, map[string]string{"test/b*": "*", "test2/*": "*"}, - map[string]string{"test/bar": "foo", "test2/123": "bar"}, true) - testAnnotationMatch(t, map[string]string{"test/b*": "*", "test2/*": "*"}, - map[string]string{"test/bar": "foo", "test2/123": "bar", "test3/123": "bar2"}, true) -} - -func testAnnotationMatch(t *testing.T, policy map[string]string, resource map[string]string, match bool) { - res := checkAnnotations(policy, resource) - if res != match { - t.Errorf("annotations %v -> labels %v: expected %v received %v", policy, resource, match, res) - } -} - func TestManagedPodResource(t *testing.T) { testCases := []struct { name string diff --git a/pkg/utils/match/annotations.go b/pkg/utils/match/annotations.go new file mode 100644 index 0000000000..96f89b683e --- /dev/null +++ b/pkg/utils/match/annotations.go @@ -0,0 +1,24 @@ +package match + +import ( + "github.com/kyverno/kyverno/pkg/utils/wildcard" +) + +func CheckAnnotations(expected map[string]string, actual map[string]string) bool { + if len(expected) == 0 { + return true + } + for k, v := range expected { + match := false + for k1, v1 := range actual { + if wildcard.Match(k, k1) && wildcard.Match(v, v1) { + match = true + break + } + } + if !match { + return false + } + } + return true +} diff --git a/pkg/utils/match/annotations_test.go b/pkg/utils/match/annotations_test.go new file mode 100644 index 0000000000..e41abfef3a --- /dev/null +++ b/pkg/utils/match/annotations_test.go @@ -0,0 +1,112 @@ +package match + +import "testing" + +func TestCheckAnnotations(t *testing.T) { + type args struct { + expected map[string]string + actual map[string]string + } + tests := []struct { + name string + args args + want bool + }{{ + args: args{ + expected: map[string]string{}, + actual: map[string]string{}, + }, + want: true, + }, { + args: args{ + expected: map[string]string{ + "test/*": "*", + }, + actual: map[string]string{}, + }, + want: false, + }, { + args: args{ + expected: map[string]string{ + "test/*": "*", + }, + actual: map[string]string{ + "tes1/test": "*", + }, + }, + want: false, + }, { + args: args{ + expected: map[string]string{ + "test/*": "*", + }, + actual: map[string]string{ + "test/test": "*", + }, + }, + want: true, + }, { + args: args{ + expected: map[string]string{ + "test/*": "*", + }, + actual: map[string]string{ + "test/bar": "foo", + }, + }, + want: true, + }, { + args: args{ + expected: map[string]string{ + "test/b*": "*", + }, + actual: map[string]string{ + "test/bar": "foo", + }, + }, + want: true, + }, { + args: args{ + expected: map[string]string{ + "test/b*": "*", + "test2/*": "*", + }, + actual: map[string]string{ + "test/bar": "foo", + }, + }, + want: false, + }, { + args: args{ + expected: map[string]string{ + "test/b*": "*", + "test2/*": "*", + }, + actual: map[string]string{ + "test/bar": "foo", + "test2/123": "bar", + }, + }, + want: true, + }, { + args: args{ + expected: map[string]string{ + "test/b*": "*", + "test2/*": "*", + }, + actual: map[string]string{ + "test/bar": "foo", + "test2/123": "bar", + "test3/123": "bar2", + }, + }, + want: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CheckAnnotations(tt.args.expected, tt.args.actual); got != tt.want { + t.Errorf("CheckAnnotations() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/utils/match/kind.go b/pkg/utils/match/kind.go new file mode 100644 index 0000000000..b20b60c5d0 --- /dev/null +++ b/pkg/utils/match/kind.go @@ -0,0 +1,41 @@ +package match + +import ( + "strings" + + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" + "golang.org/x/text/cases" + "golang.org/x/text/language" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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 { + title := cases.Title(language.Und, cases.NoLower) + result := false + 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 = title.String(kind) == gvk.Kind && + (subresourceInAdmnReview == "" || + (allowEphemeralContainers && subresourceInAdmnReview == "ephemeralcontainers")) + if gv != "" { + result = result && kubeutils.GroupVersionMatches(gv, gvk.GroupVersion().String()) + } + } + } else { + result = true + } + if result { + break + } + } + return result +} diff --git a/pkg/utils/match/match_test.go b/pkg/utils/match/kind_test.go similarity index 100% rename from pkg/utils/match/match_test.go rename to pkg/utils/match/kind_test.go diff --git a/pkg/utils/match/labels.go b/pkg/utils/match/labels.go new file mode 100644 index 0000000000..fa41880210 --- /dev/null +++ b/pkg/utils/match/labels.go @@ -0,0 +1,21 @@ +package match + +import ( + "github.com/kyverno/kyverno/pkg/engine/wildcards" + "github.com/kyverno/kyverno/pkg/logging" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +func CheckSelector(expected *metav1.LabelSelector, actual map[string]string) (bool, error) { + wildcards.ReplaceInSelector(expected, actual) + selector, err := metav1.LabelSelectorAsSelector(expected) + if err != nil { + logging.Error(err, "failed to build label selector") + return false, err + } + if selector.Matches(labels.Set(actual)) { + return true, nil + } + return false, nil +} diff --git a/pkg/utils/match/labels_test.go b/pkg/utils/match/labels_test.go new file mode 100644 index 0000000000..7ccc8ed4de --- /dev/null +++ b/pkg/utils/match/labels_test.go @@ -0,0 +1,148 @@ +package match + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestCheckSelector(t *testing.T) { + type args struct { + expected *metav1.LabelSelector + actual map[string]string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{{ + args: args{ + expected: &metav1.LabelSelector{}, + actual: map[string]string{}, + }, + want: true, + }, { + args: args{ + expected: &metav1.LabelSelector{}, + actual: map[string]string{ + "foo": "bar", + }, + }, + want: true, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test.io/*": "bar"}, + }, + actual: map[string]string{ + "foo": "bar", + }, + }, + want: false, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"scale.test.io/*": "bar"}, + }, + actual: map[string]string{ + "foo": "bar", + }, + }, + want: false, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test.io/*": "bar"}, + }, + actual: map[string]string{ + "test.io/scale": "foo", + "test.io/functional": "bar", + }, + }, + want: true, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test.io/*": "*"}, + }, + actual: map[string]string{ + "test.io/scale": "foo", + "test.io/functional": "bar", + }, + }, + want: true, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test.io/*": "a*"}, + }, + actual: map[string]string{ + "test.io/scale": "foo", + "test.io/functional": "bar", + }, + }, + want: false, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test.io/scale": "f??"}, + }, + actual: map[string]string{ + "test.io/scale": "foo", + "test.io/functional": "bar", + }, + }, + want: true, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"*": "*"}, + }, + actual: map[string]string{ + "test.io/scale": "foo", + "test.io/functional": "bar", + }, + }, + want: true, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"test.io/functional": "foo"}, + }, + actual: map[string]string{ + "test.io/scale": "foo", + "test.io/functional": "bar", + }, + }, + want: false, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"*": "*"}, + }, + actual: map[string]string{}, + }, + want: false, + }, { + args: args{ + expected: &metav1.LabelSelector{ + MatchLabels: map[string]string{"abc/def/ghi": "*"}, + }, + actual: map[string]string{}, + }, + wantErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := CheckSelector(tt.args.expected, tt.args.actual) + if (err != nil) != tt.wantErr { + t.Errorf("CheckSelector() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("CheckSelector() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/utils/match/match.go b/pkg/utils/match/match.go index b435f25d8f..61e24afca2 100644 --- a/pkg/utils/match/match.go +++ b/pkg/utils/match/match.go @@ -2,26 +2,15 @@ package match import ( "fmt" - "strings" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1" - "github.com/kyverno/kyverno/pkg/engine/wildcards" - "github.com/kyverno/kyverno/pkg/logging" datautils "github.com/kyverno/kyverno/pkg/utils/data" - kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "github.com/kyverno/kyverno/pkg/utils/wildcard" "go.uber.org/multierr" - "golang.org/x/exp/slices" - "golang.org/x/text/cases" - "golang.org/x/text/language" - 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/labels" - "k8s.io/apimachinery/pkg/runtime/schema" ) func CheckNamespace(statement string, resource unstructured.Unstructured) error { @@ -138,44 +127,13 @@ func checkUserInfo( } } if len(userInfo.Subjects) > 0 { - if !checkSubjects(userInfo.Subjects, admissionInfo.AdmissionUserInfo, excludeGroupRole) { + if !CheckSubjects(userInfo.Subjects, admissionInfo.AdmissionUserInfo, excludeGroupRole) { errs = append(errs, fmt.Errorf("user info does not match subject for the given conditionBlock")) } } return errs } -// matchSubjects return true if one of ruleSubjects exist in userInfo -func checkSubjects( - ruleSubjects []rbacv1.Subject, - userInfo authenticationv1.UserInfo, - excludeGroupRole []string, -) bool { - const SaPrefix = "system:serviceaccount:" - userGroups := append(userInfo.Groups, userInfo.Username) - // TODO: see issue https://github.com/kyverno/kyverno/issues/861 - for _, e := range excludeGroupRole { - ruleSubjects = append(ruleSubjects, rbacv1.Subject{Kind: "Group", Name: e}) - } - for _, subject := range ruleSubjects { - switch subject.Kind { - case "ServiceAccount": - if len(userInfo.Username) <= len(SaPrefix) { - continue - } - subjectServiceAccount := subject.Namespace + ":" + subject.Name - if userInfo.Username[len(SaPrefix):] == subjectServiceAccount { - return true - } - case "User", "Group": - if slices.Contains(userGroups, subject.Name) { - return true - } - } - } - return false -} - func checkResourceDescription( conditionBlock kyvernov1.ResourceDescription, resource unstructured.Unstructured, @@ -195,14 +153,14 @@ func checkResourceDescription( resourceName = resource.GetGenerateName() } if conditionBlock.Name != "" { - if !checkName(conditionBlock.Name, resourceName) { + if !CheckName(conditionBlock.Name, resourceName) { errs = append(errs, fmt.Errorf("name does not match")) } } if len(conditionBlock.Names) > 0 { noneMatch := true for i := range conditionBlock.Names { - if checkName(conditionBlock.Names[i], resourceName) { + if CheckName(conditionBlock.Names[i], resourceName) { noneMatch = false break } @@ -217,12 +175,12 @@ func checkResourceDescription( } } if len(conditionBlock.Annotations) > 0 { - if !checkAnnotations(conditionBlock.Annotations, resource.GetAnnotations()) { + if !CheckAnnotations(conditionBlock.Annotations, resource.GetAnnotations()) { errs = append(errs, fmt.Errorf("annotations does not match")) } } if conditionBlock.Selector != nil { - hasPassed, err := checkSelector(conditionBlock.Selector, resource.GetLabels()) + hasPassed, err := CheckSelector(conditionBlock.Selector, resource.GetLabels()) if err != nil { errs = append(errs, fmt.Errorf("failed to parse selector: %v", err)) } else { @@ -232,7 +190,7 @@ func checkResourceDescription( } } if conditionBlock.NamespaceSelector != nil && resource.GetKind() != "Namespace" && resource.GetKind() != "" { - hasPassed, err := checkSelector(conditionBlock.NamespaceSelector, namespaceLabels) + hasPassed, err := CheckSelector(conditionBlock.NamespaceSelector, namespaceLabels) if err != nil { errs = append(errs, fmt.Errorf("failed to parse namespace selector: %v", err)) } else { @@ -244,41 +202,6 @@ func checkResourceDescription( return errs } -// 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 { - title := cases.Title(language.Und, cases.NoLower) - result := false - 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 = title.String(kind) == gvk.Kind && - (subresourceInAdmnReview == "" || - (allowEphemeralContainers && subresourceInAdmnReview == "ephemeralcontainers")) - if gv != "" { - result = result && kubeutils.GroupVersionMatches(gv, gvk.GroupVersion().String()) - } - } - } else { - result = true - } - - if result { - break - } - } - return result -} - -func checkName(name, resourceName string) bool { - return wildcard.Match(name, resourceName) -} - func checkNameSpace(namespaces []string, resource unstructured.Unstructured) bool { resourceNameSpace := resource.GetNamespace() if resource.GetKind() == "Namespace" { @@ -291,35 +214,3 @@ func checkNameSpace(namespaces []string, resource unstructured.Unstructured) boo } return false } - -func checkAnnotations(annotations map[string]string, resourceAnnotations map[string]string) bool { - if len(annotations) == 0 { - return true - } - for k, v := range annotations { - match := false - for k1, v1 := range resourceAnnotations { - if wildcard.Match(k, k1) && wildcard.Match(v, v1) { - match = true - break - } - } - if !match { - return false - } - } - return true -} - -func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[string]string) (bool, error) { - wildcards.ReplaceInSelector(labelSelector, resourceLabels) - selector, err := metav1.LabelSelectorAsSelector(labelSelector) - if err != nil { - logging.Error(err, "failed to build label selector") - return false, err - } - if selector.Matches(labels.Set(resourceLabels)) { - return true, nil - } - return false, nil -} diff --git a/pkg/utils/match/name.go b/pkg/utils/match/name.go new file mode 100644 index 0000000000..85082372fb --- /dev/null +++ b/pkg/utils/match/name.go @@ -0,0 +1,9 @@ +package match + +import ( + "github.com/kyverno/kyverno/pkg/utils/wildcard" +) + +func CheckName(expected, actual string) bool { + return wildcard.Match(expected, actual) +} diff --git a/pkg/utils/match/name_test.go b/pkg/utils/match/name_test.go new file mode 100644 index 0000000000..dd594f04c3 --- /dev/null +++ b/pkg/utils/match/name_test.go @@ -0,0 +1,55 @@ +package match + +import "testing" + +func TestCheckName(t *testing.T) { + type args struct { + expected string + actual string + } + tests := []struct { + name string + args args + want bool + }{{ + args: args{}, + want: true, + }, { + args: args{ + expected: "", + actual: "foo", + }, + want: false, + }, { + args: args{ + expected: "*", + actual: "foo", + }, + want: true, + }, { + args: args{ + expected: "foo", + actual: "foo", + }, + want: true, + }, { + args: args{ + expected: "bar", + actual: "foo", + }, + want: false, + }, { + args: args{ + expected: "f?o", + actual: "foo", + }, + want: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CheckName(tt.args.expected, tt.args.actual); got != tt.want { + t.Errorf("CheckName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/utils/match/subjects.go b/pkg/utils/match/subjects.go new file mode 100644 index 0000000000..1561b1cbae --- /dev/null +++ b/pkg/utils/match/subjects.go @@ -0,0 +1,38 @@ +package match + +import ( + "golang.org/x/exp/slices" + authenticationv1 "k8s.io/api/authentication/v1" + rbacv1 "k8s.io/api/rbac/v1" +) + +// CheckSubjects return true if one of ruleSubjects exist in userInfo +func CheckSubjects( + ruleSubjects []rbacv1.Subject, + userInfo authenticationv1.UserInfo, + excludeGroupRole []string, +) bool { + const SaPrefix = "system:serviceaccount:" + userGroups := append(userInfo.Groups, userInfo.Username) + // TODO: see issue https://github.com/kyverno/kyverno/issues/861 + for _, e := range excludeGroupRole { + ruleSubjects = append(ruleSubjects, rbacv1.Subject{Kind: "Group", Name: e}) + } + for _, subject := range ruleSubjects { + switch subject.Kind { + case "ServiceAccount": + if len(userInfo.Username) <= len(SaPrefix) { + continue + } + subjectServiceAccount := subject.Namespace + ":" + subject.Name + if userInfo.Username[len(SaPrefix):] == subjectServiceAccount { + return true + } + case "User", "Group": + if slices.Contains(userGroups, subject.Name) { + return true + } + } + } + return false +}