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  }