| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package rbac_test |
| |
| import ( |
| "reflect" |
| "testing" |
| |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/util/diff" |
| "k8s.io/kubernetes/pkg/api/legacyscheme" |
| "k8s.io/kubernetes/pkg/apis/rbac" |
| "k8s.io/kubernetes/pkg/apis/rbac/v1" |
| |
| // install RBAC types |
| _ "k8s.io/kubernetes/pkg/apis/rbac/install" |
| ) |
| |
| // TestHelpersRoundTrip confirms that the rbac.New* helper functions produce RBAC objects that match objects |
| // that have gone through conversion and defaulting. This is required because these helper functions are |
| // used to create the bootstrap RBAC policy which is used during reconciliation. If they produced objects |
| // that did not match, reconciliation would incorrectly add duplicate data to the cluster's RBAC policy. |
| func TestHelpersRoundTrip(t *testing.T) { |
| rb := rbac.NewRoleBinding("role", "ns").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie() |
| rbcr := rbac.NewRoleBindingForClusterRole("role", "ns").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie() |
| crb := rbac.NewClusterBinding("role").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie() |
| |
| role := &rbac.Role{ |
| Rules: []rbac.PolicyRule{ |
| rbac.NewRule("verb").Groups("g").Resources("foo").RuleOrDie(), |
| rbac.NewRule("verb").URLs("/foo").RuleOrDie(), |
| }, |
| } |
| clusterRole := &rbac.ClusterRole{ |
| Rules: []rbac.PolicyRule{ |
| rbac.NewRule("verb").Groups("g").Resources("foo").RuleOrDie(), |
| rbac.NewRule("verb").URLs("/foo").RuleOrDie(), |
| }, |
| } |
| |
| for _, internalObj := range []runtime.Object{&rb, &rbcr, &crb, role, clusterRole} { |
| v1Obj, err := legacyscheme.Scheme.ConvertToVersion(internalObj, v1.SchemeGroupVersion) |
| if err != nil { |
| t.Errorf("err on %T: %v", internalObj, err) |
| continue |
| } |
| legacyscheme.Scheme.Default(v1Obj) |
| roundTrippedObj, err := legacyscheme.Scheme.ConvertToVersion(v1Obj, rbac.SchemeGroupVersion) |
| if err != nil { |
| t.Errorf("err on %T: %v", internalObj, err) |
| continue |
| } |
| if !reflect.DeepEqual(internalObj, roundTrippedObj) { |
| t.Errorf("err on %T: got difference:\n%s", internalObj, diff.ObjectDiff(internalObj, roundTrippedObj)) |
| continue |
| } |
| } |
| } |
| |
| func TestResourceMatches(t *testing.T) { |
| tests := []struct { |
| name string |
| ruleResources []string |
| combinedRequestedResource string |
| requestedSubresource string |
| expected bool |
| }{ |
| { |
| name: "all matches 01", |
| ruleResources: []string{"*"}, |
| combinedRequestedResource: "foo", |
| expected: true, |
| }, |
| { |
| name: "checks all rules", |
| ruleResources: []string{"doesn't match", "*"}, |
| combinedRequestedResource: "foo", |
| expected: true, |
| }, |
| { |
| name: "matches exact rule", |
| ruleResources: []string{"foo/bar"}, |
| combinedRequestedResource: "foo/bar", |
| requestedSubresource: "bar", |
| expected: true, |
| }, |
| { |
| name: "matches exact rule 02", |
| ruleResources: []string{"foo/bar"}, |
| combinedRequestedResource: "foo", |
| expected: false, |
| }, |
| { |
| name: "matches subresource", |
| ruleResources: []string{"*/scale"}, |
| combinedRequestedResource: "foo/scale", |
| requestedSubresource: "scale", |
| expected: true, |
| }, |
| { |
| name: "doesn't match partial subresource hit", |
| ruleResources: []string{"foo/bar", "*/other"}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: false, |
| }, |
| { |
| name: "matches subresource with multiple slashes", |
| ruleResources: []string{"*/other/segment"}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: true, |
| }, |
| { |
| name: "doesn't fail on empty", |
| ruleResources: []string{""}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: false, |
| }, |
| { |
| name: "doesn't fail on slash", |
| ruleResources: []string{"/"}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: false, |
| }, |
| { |
| name: "doesn't fail on missing subresource", |
| ruleResources: []string{"*/"}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: false, |
| }, |
| { |
| name: "doesn't match on not star", |
| ruleResources: []string{"*something/other/segment"}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: false, |
| }, |
| { |
| name: "doesn't match on something else", |
| ruleResources: []string{"something/other/segment"}, |
| combinedRequestedResource: "foo/other/segment", |
| requestedSubresource: "other/segment", |
| expected: false, |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.name, func(t *testing.T) { |
| rule := &rbac.PolicyRule{ |
| Resources: tc.ruleResources, |
| } |
| actual := rbac.ResourceMatches(rule, tc.combinedRequestedResource, tc.requestedSubresource) |
| if tc.expected != actual { |
| t.Errorf("expected %v, got %v", tc.expected, actual) |
| } |
| |
| }) |
| } |
| } |
| |
| func TestPolicyRuleBuilder(t *testing.T) { |
| tests := []struct { |
| testName string |
| verbs []string |
| groups []string |
| resources []string |
| names []string |
| urls []string |
| expected bool |
| policyRule rbac.PolicyRule |
| }{ |
| { |
| testName: "all empty", |
| verbs: nil, |
| groups: nil, |
| resources: nil, |
| names: nil, |
| urls: nil, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| { |
| testName: "normal resource case", |
| verbs: []string{"get"}, |
| groups: []string{""}, |
| resources: []string{"pod"}, |
| names: []string{"gakki"}, |
| urls: nil, |
| expected: true, |
| policyRule: rbac.PolicyRule{ |
| Verbs: []string{"get"}, |
| APIGroups: []string{""}, |
| Resources: []string{"pod"}, |
| ResourceNames: []string{"gakki"}, |
| NonResourceURLs: []string{}, |
| }, |
| }, |
| { |
| testName: "normal noResourceURLs case", |
| verbs: []string{"get"}, |
| groups: nil, |
| resources: nil, |
| names: nil, |
| urls: []string{"/api/registry/healthz"}, |
| expected: true, |
| policyRule: rbac.PolicyRule{ |
| Verbs: []string{"get"}, |
| APIGroups: []string{}, |
| Resources: []string{}, |
| ResourceNames: []string{}, |
| NonResourceURLs: []string{"/api/registry/healthz"}, |
| }, |
| }, |
| { |
| testName: "nonResourceURLs with no-empty groups", |
| verbs: []string{"get"}, |
| groups: []string{""}, |
| resources: nil, |
| names: nil, |
| urls: []string{"/api/registry/healthz"}, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| { |
| testName: "nonResourceURLs with no-empty resources", |
| verbs: []string{"get"}, |
| groups: nil, |
| resources: []string{"deployments", "secrets"}, |
| names: nil, |
| urls: []string{"/api/registry/healthz"}, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| { |
| testName: "nonResourceURLs with no-empty resourceNames", |
| verbs: []string{"get"}, |
| groups: nil, |
| resources: nil, |
| names: []string{"gakki"}, |
| urls: []string{"/api/registry/healthz"}, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| { |
| testName: "resource without apiGroups", |
| verbs: []string{"get"}, |
| groups: nil, |
| resources: []string{"pod"}, |
| names: []string{""}, |
| urls: nil, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| { |
| testName: "resourceNames with illegal verb", |
| verbs: []string{"list", "watch", "create", "deletecollection"}, |
| groups: []string{""}, |
| resources: []string{"pod"}, |
| names: []string{"gakki"}, |
| urls: nil, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| { |
| testName: "no nonResourceURLs nor resources", |
| verbs: []string{"get"}, |
| groups: []string{"rbac.authorization.k8s.io"}, |
| resources: nil, |
| names: []string{"gakki"}, |
| urls: nil, |
| expected: false, |
| policyRule: rbac.PolicyRule{}, |
| }, |
| } |
| for _, tc := range tests { |
| actual, err := rbac.NewRule(tc.verbs...).Groups(tc.groups...).Resources(tc.resources...).Names(tc.names...).URLs(tc.urls...).Rule() |
| if err != nil { |
| if tc.expected { |
| t.Error(err) |
| } else { |
| continue |
| } |
| } |
| if !reflect.DeepEqual(actual, tc.policyRule) { |
| t.Errorf("Expected %s got %s.", tc.policyRule, actual) |
| } |
| } |
| } |