github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/install/rule_checker_test.go (about) 1 package install 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9 corev1 "k8s.io/api/core/v1" 10 rbacv1 "k8s.io/api/rbac/v1" 11 apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/runtime" 14 "k8s.io/apimachinery/pkg/types" 15 "k8s.io/apimachinery/pkg/util/wait" 16 "k8s.io/client-go/informers" 17 k8sfake "k8s.io/client-go/kubernetes/fake" 18 "k8s.io/client-go/tools/cache" 19 apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" 20 21 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 22 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" 23 ) 24 25 func TestRuleSatisfied(t *testing.T) { 26 csv := &operatorsv1alpha1.ClusterServiceVersion{} 27 csv.SetName("barista-operator") 28 csv.SetUID(types.UID("barista-operator")) 29 30 sa := &corev1.ServiceAccount{} 31 sa.SetNamespace("coffee-shop") 32 sa.SetName("barista-operator") 33 sa.SetUID(types.UID("barista-operator")) 34 35 tests := []struct { 36 description string 37 namespace string 38 rule rbacv1.PolicyRule 39 existingRoles []*rbacv1.Role 40 existingRoleBindings []*rbacv1.RoleBinding 41 existingClusterRoles []*rbacv1.ClusterRole 42 existingClusterRoleBindings []*rbacv1.ClusterRoleBinding 43 expectedError string 44 satisfied bool 45 }{ 46 { 47 description: "NotSatisfied", 48 namespace: "coffee-shop", 49 rule: rbacv1.PolicyRule{ 50 APIGroups: []string{ 51 "", 52 }, 53 Verbs: []string{ 54 "*", 55 }, 56 Resources: []string{ 57 "donuts", 58 }, 59 }, 60 satisfied: false, 61 }, 62 { 63 description: "SatisfiedBySingleRole", 64 namespace: "coffee-shop", 65 rule: rbacv1.PolicyRule{ 66 APIGroups: []string{ 67 "", 68 }, 69 Verbs: []string{ 70 "*", 71 }, 72 Resources: []string{ 73 "donuts", 74 }, 75 }, 76 existingRoles: []*rbacv1.Role{ 77 { 78 ObjectMeta: metav1.ObjectMeta{ 79 Name: "coffee", 80 Namespace: "coffee-shop", 81 }, 82 Rules: []rbacv1.PolicyRule{ 83 { 84 APIGroups: []string{ 85 "", 86 }, 87 Verbs: []string{ 88 "*", 89 }, 90 Resources: []string{ 91 "donuts", 92 }, 93 }, 94 }, 95 }, 96 }, 97 existingRoleBindings: []*rbacv1.RoleBinding{ 98 { 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: "coffee", 101 Namespace: "coffee-shop", 102 }, 103 Subjects: []rbacv1.Subject{ 104 { 105 Kind: "ServiceAccount", 106 APIGroup: "", 107 Name: sa.GetName(), 108 Namespace: sa.GetNamespace(), 109 }, 110 }, 111 RoleRef: rbacv1.RoleRef{ 112 APIGroup: "rbac.authorization.k8s.io", 113 Kind: "Role", 114 Name: "coffee", 115 }, 116 }, 117 }, 118 satisfied: true, 119 }, 120 { 121 description: "NotSatisfiedByRoleOwnerConflict", 122 namespace: "coffee-shop", 123 rule: rbacv1.PolicyRule{ 124 APIGroups: []string{ 125 "", 126 }, 127 Verbs: []string{ 128 "create", 129 "update", 130 "delete", 131 }, 132 Resources: []string{ 133 "donuts", 134 }, 135 }, 136 existingRoles: []*rbacv1.Role{ 137 { 138 ObjectMeta: metav1.ObjectMeta{ 139 Name: "coffee", 140 Namespace: "coffee-shop", 141 OwnerReferences: []metav1.OwnerReference{ 142 { 143 APIVersion: "v1alpha1", 144 Kind: "ClusterServiceVersion", 145 Name: csv.GetName(), 146 UID: csv.GetUID(), 147 }, 148 { 149 APIVersion: "v1alpha1", 150 Kind: "ClusterServiceVersion", 151 Name: "big-donut", 152 UID: types.UID("big-donut"), 153 }, 154 }, 155 }, 156 Rules: []rbacv1.PolicyRule{ 157 { 158 APIGroups: []string{ 159 "", 160 }, 161 Verbs: []string{ 162 "create", 163 "update", 164 }, 165 Resources: []string{ 166 "donuts", 167 }, 168 }, 169 }, 170 }, 171 { 172 ObjectMeta: metav1.ObjectMeta{ 173 Name: "napkin", 174 Namespace: "coffee-shop", 175 OwnerReferences: []metav1.OwnerReference{ 176 { 177 APIVersion: "v1alpha1", 178 Kind: "ClusterServiceVersion", 179 Name: "big-donut", 180 UID: types.UID("big-donut"), 181 }, 182 }, 183 }, 184 Rules: []rbacv1.PolicyRule{ 185 { 186 APIGroups: []string{ 187 "", 188 }, 189 Verbs: []string{ 190 "delete", 191 }, 192 Resources: []string{ 193 "donuts", 194 }, 195 }, 196 }, 197 }, 198 }, 199 existingRoleBindings: []*rbacv1.RoleBinding{ 200 { 201 ObjectMeta: metav1.ObjectMeta{ 202 Name: "coffee", 203 Namespace: "coffee-shop", 204 }, 205 Subjects: []rbacv1.Subject{ 206 { 207 Kind: "ServiceAccount", 208 APIGroup: "", 209 Name: sa.GetName(), 210 Namespace: sa.GetNamespace(), 211 }, 212 }, 213 RoleRef: rbacv1.RoleRef{ 214 APIGroup: "rbac.authorization.k8s.io", 215 Kind: "Role", 216 Name: "coffee", 217 }, 218 }, 219 { 220 ObjectMeta: metav1.ObjectMeta{ 221 Name: "napkin", 222 Namespace: "coffee-shop", 223 }, 224 Subjects: []rbacv1.Subject{ 225 { 226 Kind: "ServiceAccount", 227 APIGroup: "", 228 Name: sa.GetName(), 229 Namespace: sa.GetNamespace(), 230 }, 231 }, 232 RoleRef: rbacv1.RoleRef{ 233 APIGroup: "rbac.authorization.k8s.io", 234 Kind: "Role", 235 Name: "napkin", 236 }, 237 }, 238 }, 239 satisfied: false, 240 }, 241 { 242 description: "SatisfiedByRoleWithConcurrentOwners", 243 namespace: "coffee-shop", 244 rule: rbacv1.PolicyRule{ 245 APIGroups: []string{ 246 "", 247 }, 248 Verbs: []string{ 249 "create", 250 "update", 251 "delete", 252 }, 253 Resources: []string{ 254 "donuts", 255 }, 256 }, 257 existingRoles: []*rbacv1.Role{ 258 { 259 ObjectMeta: metav1.ObjectMeta{ 260 Name: "coffee", 261 Namespace: "coffee-shop", 262 OwnerReferences: []metav1.OwnerReference{ 263 { 264 APIVersion: "v1alpha1", 265 Kind: "ClusterServiceVersion", 266 Name: csv.GetName(), 267 UID: csv.GetUID(), 268 }, 269 { 270 APIVersion: "v1alpha1", 271 Kind: "ClusterServiceVersion", 272 Name: "big-donut", 273 UID: types.UID("big-donut"), 274 }, 275 }, 276 }, 277 Rules: []rbacv1.PolicyRule{ 278 { 279 APIGroups: []string{ 280 "", 281 }, 282 Verbs: []string{ 283 "create", 284 "update", 285 "delete", 286 }, 287 Resources: []string{ 288 "donuts", 289 }, 290 }, 291 }, 292 }, 293 }, 294 existingRoleBindings: []*rbacv1.RoleBinding{ 295 { 296 ObjectMeta: metav1.ObjectMeta{ 297 Name: "coffee", 298 Namespace: "coffee-shop", 299 OwnerReferences: []metav1.OwnerReference{ 300 { 301 APIVersion: "", 302 Kind: "ServiceAccount", 303 Name: "mixologist", 304 UID: types.UID("mixologist"), 305 }, 306 }, 307 }, 308 Subjects: []rbacv1.Subject{ 309 { 310 Kind: "ServiceAccount", 311 APIGroup: "", 312 Name: sa.GetName(), 313 Namespace: sa.GetNamespace(), 314 }, 315 }, 316 RoleRef: rbacv1.RoleRef{ 317 APIGroup: "rbac.authorization.k8s.io", 318 Kind: "Role", 319 Name: "coffee", 320 }, 321 }, 322 }, 323 satisfied: true, 324 }, 325 { 326 description: "SatisfiedByMutlipleRoles", 327 namespace: "coffee-shop", 328 rule: rbacv1.PolicyRule{ 329 APIGroups: []string{ 330 "", 331 }, 332 Verbs: []string{ 333 "create", 334 "update", 335 "delete", 336 }, 337 Resources: []string{ 338 "donuts", 339 }, 340 }, 341 existingRoles: []*rbacv1.Role{ 342 { 343 ObjectMeta: metav1.ObjectMeta{ 344 Name: "coffee", 345 Namespace: "coffee-shop", 346 }, 347 Rules: []rbacv1.PolicyRule{ 348 { 349 APIGroups: []string{ 350 "", 351 }, 352 Verbs: []string{ 353 "create", 354 "update", 355 }, 356 Resources: []string{ 357 "donuts", 358 }, 359 }, 360 }, 361 }, 362 { 363 ObjectMeta: metav1.ObjectMeta{ 364 Name: "napkin", 365 Namespace: "coffee-shop", 366 }, 367 Rules: []rbacv1.PolicyRule{ 368 { 369 APIGroups: []string{ 370 "", 371 }, 372 Verbs: []string{ 373 "delete", 374 }, 375 Resources: []string{ 376 "donuts", 377 }, 378 }, 379 }, 380 }, 381 }, 382 existingRoleBindings: []*rbacv1.RoleBinding{ 383 { 384 ObjectMeta: metav1.ObjectMeta{ 385 Name: "coffee", 386 Namespace: "coffee-shop", 387 }, 388 Subjects: []rbacv1.Subject{ 389 { 390 Kind: "ServiceAccount", 391 APIGroup: "", 392 Name: sa.GetName(), 393 Namespace: "coffee-shop", 394 }, 395 }, 396 RoleRef: rbacv1.RoleRef{ 397 APIGroup: "rbac.authorization.k8s.io", 398 Kind: "Role", 399 Name: "coffee", 400 }, 401 }, 402 { 403 ObjectMeta: metav1.ObjectMeta{ 404 Name: "napkin", 405 Namespace: "coffee-shop", 406 }, 407 Subjects: []rbacv1.Subject{ 408 { 409 Kind: "ServiceAccount", 410 APIGroup: "", 411 Name: sa.GetName(), 412 Namespace: sa.GetNamespace(), 413 }, 414 }, 415 RoleRef: rbacv1.RoleRef{ 416 APIGroup: "rbac.authorization.k8s.io", 417 Kind: "Role", 418 Name: "napkin", 419 }, 420 }, 421 }, 422 satisfied: true, 423 }, 424 { 425 description: "RuleSatisfiedByClusterRole", 426 namespace: metav1.NamespaceAll, 427 rule: rbacv1.PolicyRule{ 428 APIGroups: []string{ 429 "", 430 }, 431 Verbs: []string{ 432 "create", 433 "update", 434 "delete", 435 }, 436 Resources: []string{ 437 "donuts", 438 }, 439 }, 440 existingClusterRoles: []*rbacv1.ClusterRole{ 441 { 442 ObjectMeta: metav1.ObjectMeta{ 443 Name: "coffee", 444 }, 445 Rules: []rbacv1.PolicyRule{ 446 { 447 APIGroups: []string{ 448 "", 449 }, 450 Verbs: []string{ 451 "*", 452 }, 453 Resources: []string{ 454 "*", 455 }, 456 }, 457 }, 458 }, 459 }, 460 existingClusterRoleBindings: []*rbacv1.ClusterRoleBinding{ 461 { 462 ObjectMeta: metav1.ObjectMeta{ 463 Name: "coffee", 464 }, 465 Subjects: []rbacv1.Subject{ 466 { 467 Kind: "ServiceAccount", 468 APIGroup: "", 469 Name: sa.GetName(), 470 Namespace: sa.GetNamespace(), 471 }, 472 }, 473 RoleRef: rbacv1.RoleRef{ 474 APIGroup: "rbac.authorization.k8s.io", 475 Kind: "ClusterRole", 476 Name: "coffee", 477 }, 478 }, 479 }, 480 satisfied: true, 481 }, 482 { 483 description: "RuleNotSatisfiedByClusterRole", 484 namespace: metav1.NamespaceAll, 485 rule: rbacv1.PolicyRule{ 486 APIGroups: []string{ 487 "", 488 }, 489 Verbs: []string{ 490 "create", 491 "update", 492 "delete", 493 }, 494 Resources: []string{ 495 "donuts", 496 }, 497 }, 498 existingClusterRoles: []*rbacv1.ClusterRole{ 499 { 500 ObjectMeta: metav1.ObjectMeta{ 501 Name: "coffee", 502 }, 503 Rules: []rbacv1.PolicyRule{ 504 { 505 APIGroups: []string{ 506 "", 507 }, 508 Verbs: []string{ 509 "delete", 510 }, 511 Resources: []string{ 512 "*", 513 }, 514 }, 515 }, 516 }, 517 }, 518 existingClusterRoleBindings: []*rbacv1.ClusterRoleBinding{ 519 { 520 ObjectMeta: metav1.ObjectMeta{ 521 Name: "coffee", 522 }, 523 Subjects: []rbacv1.Subject{ 524 { 525 Kind: "ServiceAccount", 526 APIGroup: "", 527 Name: sa.GetName(), 528 Namespace: sa.GetNamespace(), 529 }, 530 }, 531 RoleRef: rbacv1.RoleRef{ 532 APIGroup: "rbac.authorization.k8s.io", 533 Kind: "ClusterRole", 534 Name: "coffee", 535 }, 536 }, 537 }, 538 satisfied: false, 539 }, 540 } 541 542 for _, tt := range tests { 543 t.Run(tt.description, func(t *testing.T) { 544 // create existing objects 545 k8sObjs := Objs(tt.existingRoles, 546 tt.existingRoleBindings, 547 tt.existingClusterRoles, 548 tt.existingClusterRoleBindings, 549 ) 550 551 // create the fake CSVRuleChecker 552 stopCh := make(chan struct{}) 553 defer func() { close(stopCh) }() 554 555 t.Logf("calling NewFakeCSVRuleChecker...") 556 ruleChecker, err := NewFakeCSVRuleChecker(k8sObjs, csv, tt.namespace, stopCh) 557 require.NoError(t, err) 558 t.Logf("NewFakeCSVRuleChecker returned") 559 time.Sleep(1 * time.Second) 560 561 t.Logf("checking if rules are satisfied...") 562 // check if the rule is satisfied 563 satisfied, err := ruleChecker.RuleSatisfied(sa, tt.namespace, tt.rule) 564 if tt.expectedError != "" { 565 require.Error(t, err, "an error was expected") 566 require.Equal(t, tt.expectedError, err.Error, "error did not match expected error") 567 } 568 569 t.Logf("after checking if satisfied") 570 require.Equal(t, tt.satisfied, satisfied) 571 }) 572 } 573 } 574 575 func NewFakeCSVRuleChecker(k8sObjs []runtime.Object, csv *operatorsv1alpha1.ClusterServiceVersion, namespace string, stopCh <-chan struct{}) (*CSVRuleChecker, error) { 576 // create client fakes 577 opClientFake := operatorclient.NewClient(k8sfake.NewSimpleClientset(k8sObjs...), apiextensionsfake.NewSimpleClientset(), apiregistrationfake.NewSimpleClientset()) 578 579 // create test namespace 580 if namespace != metav1.NamespaceAll { 581 _, err := opClientFake.KubernetesInterface().CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}) 582 if err != nil { 583 return nil, err 584 } 585 } 586 587 informerFactory := informers.NewSharedInformerFactory(opClientFake.KubernetesInterface(), 1*time.Second) 588 roleInformer := informerFactory.Rbac().V1().Roles() 589 roleBindingInformer := informerFactory.Rbac().V1().RoleBindings() 590 clusterRoleInformer := informerFactory.Rbac().V1().ClusterRoles() 591 clusterRoleBindingInformer := informerFactory.Rbac().V1().ClusterRoleBindings() 592 593 // kick off informers 594 for _, informer := range []cache.SharedIndexInformer{roleInformer.Informer(), roleBindingInformer.Informer(), clusterRoleInformer.Informer(), clusterRoleBindingInformer.Informer()} { 595 go informer.Run(stopCh) 596 597 synced := func(_ context.Context) (done bool, err error) { 598 return informer.HasSynced(), nil 599 } 600 601 // wait until the informer has synced to continue 602 if err := wait.PollUntilContextTimeout(context.Background(), 500*time.Millisecond, 5*time.Second, true, synced); err != nil { 603 return nil, err 604 } 605 } 606 607 ruleChecker := NewCSVRuleChecker(roleInformer.Lister(), roleBindingInformer.Lister(), clusterRoleInformer.Lister(), clusterRoleBindingInformer.Lister(), csv) 608 609 return ruleChecker, nil 610 } 611 612 func Objs(roles []*rbacv1.Role, roleBindings []*rbacv1.RoleBinding, clusterRoles []*rbacv1.ClusterRole, clusterRoleBindings []*rbacv1.ClusterRoleBinding) []runtime.Object { 613 k8sObjs := make([]runtime.Object, 0, len(roles)+len(roleBindings)+len(clusterRoles)+len(clusterRoleBindings)) 614 for _, role := range roles { 615 k8sObjs = append(k8sObjs, role) 616 } 617 618 for _, roleBinding := range roleBindings { 619 k8sObjs = append(k8sObjs, roleBinding) 620 } 621 622 for _, clusterRole := range clusterRoles { 623 k8sObjs = append(k8sObjs, clusterRole) 624 } 625 626 for _, clusterRoleBinding := range clusterRoleBindings { 627 k8sObjs = append(k8sObjs, clusterRoleBinding) 628 } 629 630 return k8sObjs 631 }