k8s.io/kubernetes@v1.29.3/pkg/apis/rbac/helpers_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rbac_test 18 19 import ( 20 "reflect" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "k8s.io/apimachinery/pkg/runtime" 25 "k8s.io/kubernetes/pkg/api/legacyscheme" 26 "k8s.io/kubernetes/pkg/apis/rbac" 27 v1 "k8s.io/kubernetes/pkg/apis/rbac/v1" 28 29 // install RBAC types 30 _ "k8s.io/kubernetes/pkg/apis/rbac/install" 31 ) 32 33 // TestHelpersRoundTrip confirms that the rbac.New* helper functions produce RBAC objects that match objects 34 // that have gone through conversion and defaulting. This is required because these helper functions are 35 // used to create the bootstrap RBAC policy which is used during reconciliation. If they produced objects 36 // that did not match, reconciliation would incorrectly add duplicate data to the cluster's RBAC policy. 37 func TestHelpersRoundTrip(t *testing.T) { 38 rb := rbac.NewRoleBinding("role", "ns").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie() 39 rbcr := rbac.NewRoleBindingForClusterRole("role", "ns").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie() 40 crb := rbac.NewClusterBinding("role").Groups("g").SAs("ns", "sa").Users("u").BindingOrDie() 41 42 role := &rbac.Role{ 43 Rules: []rbac.PolicyRule{ 44 rbac.NewRule("verb").Groups("g").Resources("foo").RuleOrDie(), 45 rbac.NewRule("verb").URLs("/foo").RuleOrDie(), 46 }, 47 } 48 clusterRole := &rbac.ClusterRole{ 49 Rules: []rbac.PolicyRule{ 50 rbac.NewRule("verb").Groups("g").Resources("foo").RuleOrDie(), 51 rbac.NewRule("verb").URLs("/foo").RuleOrDie(), 52 }, 53 } 54 55 for _, internalObj := range []runtime.Object{&rb, &rbcr, &crb, role, clusterRole} { 56 v1Obj, err := legacyscheme.Scheme.ConvertToVersion(internalObj, v1.SchemeGroupVersion) 57 if err != nil { 58 t.Errorf("err on %T: %v", internalObj, err) 59 continue 60 } 61 legacyscheme.Scheme.Default(v1Obj) 62 roundTrippedObj, err := legacyscheme.Scheme.ConvertToVersion(v1Obj, rbac.SchemeGroupVersion) 63 if err != nil { 64 t.Errorf("err on %T: %v", internalObj, err) 65 continue 66 } 67 if !reflect.DeepEqual(internalObj, roundTrippedObj) { 68 t.Errorf("err on %T: got difference:\n%s", internalObj, cmp.Diff(internalObj, roundTrippedObj)) 69 continue 70 } 71 } 72 } 73 74 func TestResourceMatches(t *testing.T) { 75 tests := []struct { 76 name string 77 ruleResources []string 78 combinedRequestedResource string 79 requestedSubresource string 80 expected bool 81 }{ 82 { 83 name: "all matches 01", 84 ruleResources: []string{"*"}, 85 combinedRequestedResource: "foo", 86 expected: true, 87 }, 88 { 89 name: "checks all rules", 90 ruleResources: []string{"doesn't match", "*"}, 91 combinedRequestedResource: "foo", 92 expected: true, 93 }, 94 { 95 name: "matches exact rule", 96 ruleResources: []string{"foo/bar"}, 97 combinedRequestedResource: "foo/bar", 98 requestedSubresource: "bar", 99 expected: true, 100 }, 101 { 102 name: "matches exact rule 02", 103 ruleResources: []string{"foo/bar"}, 104 combinedRequestedResource: "foo", 105 expected: false, 106 }, 107 { 108 name: "matches subresource", 109 ruleResources: []string{"*/scale"}, 110 combinedRequestedResource: "foo/scale", 111 requestedSubresource: "scale", 112 expected: true, 113 }, 114 { 115 name: "doesn't match partial subresource hit", 116 ruleResources: []string{"foo/bar", "*/other"}, 117 combinedRequestedResource: "foo/other/segment", 118 requestedSubresource: "other/segment", 119 expected: false, 120 }, 121 { 122 name: "matches subresource with multiple slashes", 123 ruleResources: []string{"*/other/segment"}, 124 combinedRequestedResource: "foo/other/segment", 125 requestedSubresource: "other/segment", 126 expected: true, 127 }, 128 { 129 name: "doesn't fail on empty", 130 ruleResources: []string{""}, 131 combinedRequestedResource: "foo/other/segment", 132 requestedSubresource: "other/segment", 133 expected: false, 134 }, 135 { 136 name: "doesn't fail on slash", 137 ruleResources: []string{"/"}, 138 combinedRequestedResource: "foo/other/segment", 139 requestedSubresource: "other/segment", 140 expected: false, 141 }, 142 { 143 name: "doesn't fail on missing subresource", 144 ruleResources: []string{"*/"}, 145 combinedRequestedResource: "foo/other/segment", 146 requestedSubresource: "other/segment", 147 expected: false, 148 }, 149 { 150 name: "doesn't match on not star", 151 ruleResources: []string{"*something/other/segment"}, 152 combinedRequestedResource: "foo/other/segment", 153 requestedSubresource: "other/segment", 154 expected: false, 155 }, 156 { 157 name: "doesn't match on something else", 158 ruleResources: []string{"something/other/segment"}, 159 combinedRequestedResource: "foo/other/segment", 160 requestedSubresource: "other/segment", 161 expected: false, 162 }, 163 } 164 165 for _, tc := range tests { 166 t.Run(tc.name, func(t *testing.T) { 167 rule := &rbac.PolicyRule{ 168 Resources: tc.ruleResources, 169 } 170 actual := rbac.ResourceMatches(rule, tc.combinedRequestedResource, tc.requestedSubresource) 171 if tc.expected != actual { 172 t.Errorf("expected %v, got %v", tc.expected, actual) 173 } 174 175 }) 176 } 177 } 178 179 func TestPolicyRuleBuilder(t *testing.T) { 180 tests := []struct { 181 testName string 182 verbs []string 183 groups []string 184 resources []string 185 names []string 186 urls []string 187 expected bool 188 policyRule rbac.PolicyRule 189 }{ 190 { 191 testName: "all empty", 192 verbs: nil, 193 groups: nil, 194 resources: nil, 195 names: nil, 196 urls: nil, 197 expected: false, 198 policyRule: rbac.PolicyRule{}, 199 }, 200 { 201 testName: "normal resource case", 202 verbs: []string{"get"}, 203 groups: []string{""}, 204 resources: []string{"pod"}, 205 names: []string{"gakki"}, 206 urls: nil, 207 expected: true, 208 policyRule: rbac.PolicyRule{ 209 Verbs: []string{"get"}, 210 APIGroups: []string{""}, 211 Resources: []string{"pod"}, 212 ResourceNames: []string{"gakki"}, 213 NonResourceURLs: []string{}, 214 }, 215 }, 216 { 217 testName: "normal noResourceURLs case", 218 verbs: []string{"get"}, 219 groups: nil, 220 resources: nil, 221 names: nil, 222 urls: []string{"/api/registry/healthz"}, 223 expected: true, 224 policyRule: rbac.PolicyRule{ 225 Verbs: []string{"get"}, 226 APIGroups: []string{}, 227 Resources: []string{}, 228 ResourceNames: []string{}, 229 NonResourceURLs: []string{"/api/registry/healthz"}, 230 }, 231 }, 232 { 233 testName: "nonResourceURLs with no-empty groups", 234 verbs: []string{"get"}, 235 groups: []string{""}, 236 resources: nil, 237 names: nil, 238 urls: []string{"/api/registry/healthz"}, 239 expected: false, 240 policyRule: rbac.PolicyRule{}, 241 }, 242 { 243 testName: "nonResourceURLs with no-empty resources", 244 verbs: []string{"get"}, 245 groups: nil, 246 resources: []string{"deployments", "secrets"}, 247 names: nil, 248 urls: []string{"/api/registry/healthz"}, 249 expected: false, 250 policyRule: rbac.PolicyRule{}, 251 }, 252 { 253 testName: "nonResourceURLs with no-empty resourceNames", 254 verbs: []string{"get"}, 255 groups: nil, 256 resources: nil, 257 names: []string{"gakki"}, 258 urls: []string{"/api/registry/healthz"}, 259 expected: false, 260 policyRule: rbac.PolicyRule{}, 261 }, 262 { 263 testName: "resource without apiGroups", 264 verbs: []string{"get"}, 265 groups: nil, 266 resources: []string{"pod"}, 267 names: []string{""}, 268 urls: nil, 269 expected: false, 270 policyRule: rbac.PolicyRule{}, 271 }, 272 { 273 testName: "resourceNames with illegal verb", 274 verbs: []string{"list", "watch", "create", "deletecollection"}, 275 groups: []string{""}, 276 resources: []string{"pod"}, 277 names: []string{"gakki"}, 278 urls: nil, 279 expected: false, 280 policyRule: rbac.PolicyRule{}, 281 }, 282 { 283 testName: "no nonResourceURLs nor resources", 284 verbs: []string{"get"}, 285 groups: []string{"rbac.authorization.k8s.io"}, 286 resources: nil, 287 names: []string{"gakki"}, 288 urls: nil, 289 expected: false, 290 policyRule: rbac.PolicyRule{}, 291 }, 292 } 293 for _, tc := range tests { 294 actual, err := rbac.NewRule(tc.verbs...).Groups(tc.groups...).Resources(tc.resources...).Names(tc.names...).URLs(tc.urls...).Rule() 295 if err != nil { 296 if tc.expected { 297 t.Error(err) 298 } else { 299 continue 300 } 301 } 302 if !reflect.DeepEqual(actual, tc.policyRule) { 303 t.Errorf("Expected %s got %s.", tc.policyRule, actual) 304 } 305 } 306 }