github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/multicluster/verify-permissions/verify_permissions_test.go (about)

     1  // Copyright (c) 2021, 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  package permissions_test
     4  
     5  import (
     6  	"context"
     7  	goerrors "errors"
     8  	"fmt"
     9  
    10  	"github.com/hashicorp/go-retryablehttp"
    11  	v1alpha12 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    12  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    13  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework/metrics"
    14  
    15  	"os"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    20  
    21  	oamv1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    22  	. "github.com/onsi/ginkgo/v2"
    23  	. "github.com/onsi/gomega"
    24  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    25  	"github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    26  	"github.com/verrazzano/verrazzano/pkg/constants"
    27  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    28  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    29  	v1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/client-go/kubernetes"
    35  	"k8s.io/client-go/tools/clientcmd"
    36  	"sigs.k8s.io/controller-runtime/pkg/client"
    37  )
    38  
    39  const waitTimeout = 10 * time.Minute
    40  const pollingInterval = 10 * time.Second
    41  
    42  const testNamespace = "multiclustertest"
    43  const permissionTest1Namespace = "permissions-test1-ns"
    44  const permissionTest2Namespace = "permissions-test2-ns"
    45  
    46  var managedClusterName = os.Getenv("MANAGED_CLUSTER_NAME")
    47  var adminKubeconfig = os.Getenv("ADMIN_KUBECONFIG")
    48  var managedKubeconfig = os.Getenv("MANAGED_KUBECONFIG")
    49  var rancherProxyKubeconfig string
    50  
    51  const vpTest1 = "permissions-test1"
    52  const vpTest2 = "permissions-test2"
    53  
    54  var t = framework.NewTestFramework("permissions_test")
    55  
    56  var beforeSuite = t.BeforeSuiteFunc(func() {
    57  	// Do set up for multi cluster tests
    58  	deployTestResources()
    59  
    60  	httpClient := pkg.EventuallyVerrazzanoRetryableHTTPClient()
    61  
    62  	Eventually(func() error {
    63  		var err error
    64  		rancherProxyKubeconfig, err = getUserKubeconfigForManagedCluster(httpClient)
    65  		return err
    66  	}).WithPolling(pollingInterval).WithTimeout(time.Minute).ShouldNot(HaveOccurred())
    67  })
    68  
    69  var _ = BeforeSuite(beforeSuite)
    70  
    71  var afterSuite = t.AfterSuiteFunc(func() {
    72  	if len(rancherProxyKubeconfig) > 0 {
    73  		os.Remove(rancherProxyKubeconfig)
    74  	}
    75  
    76  	// Do set up for multi cluster tests
    77  	undeployTestResources()
    78  })
    79  
    80  var _ = AfterSuite(afterSuite)
    81  
    82  var _ = t.AfterEach(func() {})
    83  
    84  var _ = t.Describe("Multi Cluster Verify Kubeconfig Permissions", Label("f:multicluster.register"), func() {
    85  
    86  	// vZ-2336: Be able to read MultiClusterXXX resources in the admin cluster
    87  	//			Be able to update the status of MultiClusterXXX resources in the admin cluster
    88  	t.Context("In Admin Cluster, verify mc resources and their status updates.", func() {
    89  		t.BeforeEach(func() {
    90  			_ = os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("ADMIN_KUBECONFIG"))
    91  		})
    92  
    93  		t.It("Verify mc config map", func() {
    94  			Eventually(func() (bool, error) {
    95  				return findMultiClusterConfigMap(testNamespace, "mymcconfigmap")
    96  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find mc configmap")
    97  
    98  			eventuallyIterations := 0
    99  			Eventually(func() bool {
   100  				// Verify we have the expected status update
   101  				configMap := clustersv1alpha1.MultiClusterConfigMap{}
   102  				err := getMultiClusterResource(testNamespace, "mymcconfigmap", &configMap)
   103  				pkg.Log(pkg.Debug, fmt.Sprintf("Size of clusters array: %d", len(configMap.Status.Clusters)))
   104  				if len(configMap.Status.Clusters) > 0 {
   105  					pkg.Log(pkg.Debug, string("cluster reported status: "+configMap.Status.Clusters[0].State))
   106  					pkg.Log(pkg.Debug, "cluster reported name: "+configMap.Status.Clusters[0].Name)
   107  				}
   108  				eventuallyIterations++
   109  				if eventuallyIterations >= 30 && eventuallyIterations%10 == 0 {
   110  					pkg.Log(pkg.Info, "Dumping Status of config map mymcconfigmap every 10 iterations of Eventually block after we hit 30 iterations")
   111  					pkg.Log(pkg.Info, fmt.Sprintf("Conditions: %v", configMap.Status.Conditions))
   112  					pkg.Log(pkg.Info, fmt.Sprintf("Clusters: %v", configMap.Status.Clusters))
   113  					pkg.Log(pkg.Info, fmt.Sprintf("State: %v", configMap.Status.State))
   114  				}
   115  				return err == nil && configMap.Status.State == clustersv1alpha1.Succeeded &&
   116  					isStatusAsExpected(configMap.Status, clustersv1alpha1.DeployComplete, "created", clustersv1alpha1.Succeeded, managedClusterName)
   117  			}, waitTimeout, pollingInterval).Should(BeTrue())
   118  		})
   119  
   120  		t.It("Verify mc secret", func() {
   121  			Eventually(func() (bool, error) {
   122  				return findMultiClusterSecret(permissionTest1Namespace, "mymcsecret")
   123  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find mc secret")
   124  
   125  			Eventually(func() bool {
   126  				// Verify we have the expected status update
   127  				secret := clustersv1alpha1.MultiClusterSecret{}
   128  				err := getMultiClusterResource(permissionTest1Namespace, "mymcsecret", &secret)
   129  				pkg.Log(pkg.Debug, fmt.Sprintf("Size of clusters array: %d", len(secret.Status.Clusters)))
   130  				if len(secret.Status.Clusters) > 0 {
   131  					pkg.Log(pkg.Debug, string("cluster reported status: "+secret.Status.Clusters[0].State))
   132  					pkg.Log(pkg.Debug, "cluster reported name: "+secret.Status.Clusters[0].Name)
   133  				}
   134  				return err == nil && secret.Status.State == clustersv1alpha1.Succeeded &&
   135  					isStatusAsExpected(secret.Status, clustersv1alpha1.DeployComplete, "created", clustersv1alpha1.Succeeded, managedClusterName)
   136  			}, waitTimeout, pollingInterval).Should(BeTrue())
   137  		})
   138  
   139  		// VZ-2336: Be able to update the status of a VerrazzanoManagedCluster resource
   140  		t.It("vmc status updates", func() {
   141  			Eventually(func() bool {
   142  				// Verify we have the expected status update
   143  				vmc := v1alpha12.VerrazzanoManagedCluster{}
   144  				err := getMultiClusterResource("verrazzano-mc", managedClusterName, &vmc)
   145  				return err == nil && vmc.Status.LastAgentConnectTime.After(time.Now().Add(-30*time.Minute))
   146  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find VerrazzanoManagedCluster")
   147  		})
   148  
   149  	})
   150  
   151  	t.Context("In the Managed Cluster, check for ", func() {
   152  		t.BeforeEach(func() {
   153  			_ = os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("MANAGED_KUBECONFIG"))
   154  		})
   155  
   156  		t.It("the expected mc and underlying configmap", func() {
   157  			pkg.Concurrently(
   158  				func() {
   159  					Eventually(func() (bool, error) {
   160  						return findConfigMap(testNamespace, "mymcconfigmap")
   161  					}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find configmap")
   162  				},
   163  				func() {
   164  					Eventually(func() (bool, error) {
   165  						return findMultiClusterConfigMap(testNamespace, "mymcconfigmap")
   166  					}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find mc configmap")
   167  				},
   168  			)
   169  		})
   170  
   171  		t.It("the expected mc and underlying secret", func() {
   172  			pkg.Concurrently(
   173  				func() {
   174  					Eventually(func() (bool, error) {
   175  						return findSecret(permissionTest1Namespace, "mymcsecret")
   176  					}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find secret")
   177  				},
   178  				func() {
   179  					Eventually(func() (bool, error) {
   180  						return findMultiClusterSecret(permissionTest1Namespace, "mymcsecret")
   181  					}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find mc secret")
   182  				},
   183  			)
   184  		})
   185  	})
   186  
   187  	// VZ-2336:  NOT be able to update or delete any MultiClusterXXX resources in the admin cluster
   188  	t.Context("Managed Cluster", func() {
   189  		t.BeforeEach(func() {
   190  			_ = os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("MANAGED_ACCESS_KUBECONFIG"))
   191  		})
   192  
   193  		t.It("can access MultiClusterConfigMap but not modify it on admin", func() {
   194  			Eventually(func() (bool, error) {
   195  				return findMultiClusterConfigMap(testNamespace, "mymcconfigmap")
   196  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find mc configmap")
   197  			// try to update
   198  			Eventually(func() (bool, error) {
   199  				file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_configmap_update.yaml")
   200  				if err != nil {
   201  					return false, fmt.Errorf(fmt.Sprintf("expected error from CreateOrUpdateResourceFromFile: %v", err))
   202  				}
   203  				err = resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   204  				// if we didn't get an error, fail immediately
   205  				if err == nil {
   206  					return false, goerrors.New("expected error from CreateOrUpdateResourceFromFile")
   207  				}
   208  				return errors.IsForbidden(err), nil
   209  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   210  			// try to delete
   211  			Eventually(func() (bool, error) {
   212  				file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_configmap.yaml")
   213  				if err != nil {
   214  					return false, fmt.Errorf(fmt.Sprintf("expected error message from DeleteResourceFromFile: %v", err))
   215  				}
   216  				err = resource.DeleteResourceFromFile(file, t.Logs)
   217  				// if we didn't get an error, fail immediately
   218  				if err == nil {
   219  					return false, goerrors.New("expected error from DeleteResourceFromFile")
   220  				}
   221  				return errors.IsForbidden(err), nil
   222  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   223  		})
   224  
   225  		t.It("can access MultiClusterSecret but not modify it on admin", func() {
   226  			Eventually(func() (bool, error) {
   227  				return findMultiClusterSecret(permissionTest1Namespace, "mymcsecret")
   228  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find mc secret")
   229  			// try to update
   230  			Eventually(func() (bool, error) {
   231  				file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-multicluster-secret-update.yaml")
   232  				if err != nil {
   233  					return false, fmt.Errorf(fmt.Sprintf("expected error from CreateOrUpdateResourceFromFile: %v", err))
   234  				}
   235  				err = resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   236  				// if we didn't get an error, fail immediately
   237  				if err == nil {
   238  					return false, goerrors.New("expected error from CreateOrUpdateResourceFromFile")
   239  				}
   240  				return errors.IsForbidden(err), nil
   241  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   242  			// try to delete
   243  			Eventually(func() (bool, error) {
   244  				file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_secret_permissiontest1.yaml")
   245  				if err != nil {
   246  					return false, fmt.Errorf(fmt.Sprintf("expected error message from DeleteResourceFromFile: %v", err))
   247  				}
   248  				err = resource.DeleteResourceFromFile(file, t.Logs)
   249  				// if we didn't get an error, fail immediately
   250  				if err == nil {
   251  					return false, goerrors.New("expected error from DeleteResourceFromFile")
   252  				}
   253  				return errors.IsForbidden(err), nil
   254  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   255  		})
   256  
   257  		t.It("can access OAM Component but not modify it on admin", func() {
   258  			Eventually(func() (bool, error) {
   259  				return findOAMComponent(permissionTest1Namespace, "oam-component")
   260  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find OAM Component")
   261  			// try to update
   262  			Eventually(func() (bool, error) {
   263  				file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-oam-component.yaml")
   264  				if err != nil {
   265  					return false, fmt.Errorf(fmt.Sprintf("expected error from CreateOrUpdateResourceFromFile: %v", err))
   266  				}
   267  				err = resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   268  				// if we didn't get an error, fail immediately
   269  				if err == nil {
   270  					return false, goerrors.New("expected error from CreateOrUpdateResourceFromFile")
   271  				}
   272  				return errors.IsForbidden(err), nil
   273  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   274  			// try to delete
   275  			Eventually(func() (bool, error) {
   276  				file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-oam-component.yaml")
   277  				if err != nil {
   278  					return false, fmt.Errorf(fmt.Sprintf("exepected error message from DeleteResourceFromFile: %v", err))
   279  				}
   280  				err = resource.DeleteResourceFromFile(file, t.Logs)
   281  				// if we didn't get an error, fail immediately
   282  				if err == nil {
   283  					return false, goerrors.New("expected error from DeleteResourceFromFile")
   284  				}
   285  				return errors.IsForbidden(err), nil
   286  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   287  		})
   288  
   289  		t.It("can access secrets on admin from a namespace placed by a VerrazzanoProject", func() {
   290  			Eventually(func() (bool, error) {
   291  				return findSecret(permissionTest1Namespace, "mysecret")
   292  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find Secret")
   293  			// try to update
   294  			Eventually(func() (bool, error) {
   295  				file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-secret.yaml")
   296  				if err != nil {
   297  					return false, fmt.Errorf(fmt.Sprintf("expected error from CreateOrUpdateResourceFromFile: %v", err))
   298  				}
   299  				err = resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   300  				// if we didn't get an error, fail immediately
   301  				if err == nil {
   302  					return false, goerrors.New("expected error from CreateOrUpdateResourceFromFile")
   303  				}
   304  				return errors.IsForbidden(err), nil
   305  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   306  			// try to delete
   307  			Eventually(func() (bool, error) {
   308  				file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-secret.yaml")
   309  				if err != nil {
   310  					return false, fmt.Errorf(fmt.Sprintf("expected error message from DeleteResourceFromFile: %v", err))
   311  				}
   312  				err = resource.DeleteResourceFromFile(file, t.Logs)
   313  				// if we didn't get an error, fail immediately
   314  				if err == nil {
   315  					return false, goerrors.New("expected error from DeleteResourceFromFile")
   316  				}
   317  				return errors.IsForbidden(err), nil
   318  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   319  		})
   320  
   321  		t.It("cannot access secrets on admin for namespaces not placed by a VerrazzanoProject", func() {
   322  
   323  			// Expect success while namespace is placed on the managed cluster
   324  			Eventually(func() (bool, error) {
   325  				return findSecret(permissionTest2Namespace, "mysecret")
   326  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to find Secret")
   327  			// Change the placement to be on the admin cluster
   328  			pkg.Log(pkg.Info, fmt.Sprintf("Change the placement of the namespace %s to be on the admin cluster", permissionTest2Namespace))
   329  			Eventually(func() error {
   330  				file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest2-verrazzanoproject-new-placement.yaml")
   331  				if err != nil {
   332  					return err
   333  				}
   334  				return resource.CreateOrUpdateResourceFromFileInCluster(file, adminKubeconfig)
   335  			}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   336  			// Wait for the project resource to be deleted from the managed cluster
   337  			pkg.Log(pkg.Info, "Wait for the VerrazzanoProject to be removed from the managed cluster")
   338  			Eventually(func() (bool, error) {
   339  				return pkg.DoesVerrazzanoProjectExistInCluster(vpTest2, managedKubeconfig)
   340  			}, waitTimeout, pollingInterval).Should(BeFalse(), fmt.Sprintf("Expected VerrazzanoProject %s to be removed from managed cluster", vpTest2))
   341  			Eventually(func() (bool, error) {
   342  				return findSecret(permissionTest2Namespace, "mysecret")
   343  			}, waitTimeout, pollingInterval).Should(BeFalse(), "Expected to get a forbidden error")
   344  		})
   345  
   346  		// VZ-2336: NOT be able to update or delete any VerrazzanoManagedCluster resources
   347  		t.It("cannot modify vmc on admin", func() {
   348  			cluster := v1alpha12.VerrazzanoManagedCluster{}
   349  			Eventually(func() error {
   350  				return getMultiClusterResource("verrazzano-mc", managedClusterName, &cluster)
   351  			}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   352  			// try to update
   353  			Eventually(func() (bool, error) {
   354  				cluster.Spec.Description = "new Description"
   355  				err := updateObject(&cluster)
   356  				// if we didn't get an error, fail immediately
   357  				if err == nil {
   358  					return false, goerrors.New("expected error from updateObject")
   359  				}
   360  				return errors.IsForbidden(err), nil
   361  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   362  			// try to delete
   363  			Eventually(func() (bool, error) {
   364  				err := deleteObject(&cluster)
   365  				// if we didn't get an error, fail immediately
   366  				if err == nil {
   367  					return false, goerrors.New("expected error from deleteObject")
   368  				}
   369  				return errors.IsForbidden(err), nil
   370  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   371  		})
   372  
   373  		// VZ-2336: NOT be able to read other resources such as config maps in the admin cluster
   374  		t.It("cannot access resources in other namespaces", func() {
   375  			Eventually(func() (bool, error) {
   376  				err := listResource("verrazzano-system", &v1.ConfigMapList{})
   377  				// if we didn't get an error, return false to retry
   378  				if err == nil {
   379  					return false, goerrors.New("expected error from listResource")
   380  				}
   381  				return errors.IsForbidden(err), nil
   382  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   383  			Eventually(func() (bool, error) {
   384  				err := listResource("verrazzano-mc", &v1.ConfigMapList{})
   385  				// if we didn't get an error, return false to retry
   386  				if err == nil {
   387  					return false, goerrors.New("expected error from listResource")
   388  				}
   389  				return errors.IsForbidden(err), nil
   390  			}, waitTimeout, pollingInterval).Should(BeTrue(), "expected to get a forbidden error")
   391  			Eventually(func() (bool, error) {
   392  				err := listResource(testNamespace, &v1.ConfigMapList{})
   393  				// if we didn't get an error, fail immediately
   394  				if err == nil {
   395  					return false, goerrors.New("expected error from listResource")
   396  				}
   397  				return errors.IsForbidden(err), nil
   398  			}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected to get a forbidden error")
   399  		})
   400  	})
   401  
   402  	t.When("the Rancher MC user accesses Kubernetes resources on the managed cluster", func() {
   403  		var clientset *kubernetes.Clientset
   404  		t.BeforeEach(func() {
   405  			var err error
   406  			clientset, err = pkg.GetKubernetesClientsetForCluster(rancherProxyKubeconfig)
   407  			Expect(err).ShouldNot(HaveOccurred())
   408  		})
   409  
   410  		t.It("should be able to list secrets", func() {
   411  			Eventually(func() (*v1.SecretList, error) {
   412  				return clientset.CoreV1().Secrets(constants.VerrazzanoSystemNamespace).List(context.TODO(), metav1.ListOptions{})
   413  			}).WithPolling(pollingInterval).WithTimeout(time.Minute).ShouldNot(BeNil())
   414  		})
   415  
   416  		t.It("should not be able to list pods", func() {
   417  			_, err := clientset.CoreV1().Pods(constants.VerrazzanoSystemNamespace).List(context.TODO(), metav1.ListOptions{})
   418  			Expect(errors.IsForbidden(err)).To(BeTrue(), "Expected forbidden error", err)
   419  		})
   420  	})
   421  })
   422  
   423  // updateObject updates a resource using the provided object
   424  func updateObject(object client.Object) error {
   425  	clustersClient, err := getClustersClient()
   426  	if err != nil {
   427  		return err
   428  	}
   429  	err = clustersClient.Create(context.TODO(), object)
   430  	if err != nil && errors.IsAlreadyExists(err) {
   431  		err = clustersClient.Update(context.TODO(), object)
   432  	}
   433  
   434  	return err
   435  }
   436  
   437  // deleteObject deletes the given object
   438  func deleteObject(object client.Object) error {
   439  	clustersClient, err := getClustersClient()
   440  	if err != nil {
   441  		return err
   442  	}
   443  	return clustersClient.Delete(context.TODO(), object)
   444  }
   445  
   446  // deployTestResources deploys the test associated multi cluster resources
   447  func deployTestResources() {
   448  	pkg.Log(pkg.Info, "Deploying MC Resources")
   449  
   450  	_ = os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("ADMIN_KUBECONFIG"))
   451  	start := time.Now()
   452  	// create the test projects
   453  	pkg.Log(pkg.Info, "Creating test projects")
   454  	Eventually(func() error {
   455  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-verrazzanoproject.yaml")
   456  		if err != nil {
   457  			return err
   458  		}
   459  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   460  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   461  	Eventually(func() error {
   462  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest2-verrazzanoproject.yaml")
   463  		if err != nil {
   464  			return err
   465  		}
   466  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   467  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   468  
   469  	// Wait for the namespaces to be created
   470  	pkg.Log(pkg.Info, "Wait for the project namespaces to be created")
   471  	Eventually(func() (bool, error) {
   472  		return pkg.DoesNamespaceExist(testNamespace)
   473  	}, waitTimeout, pollingInterval).Should(BeTrue(), fmt.Sprintf("Expected to find namespace %s", testNamespace))
   474  	Eventually(func() (bool, error) {
   475  		return pkg.DoesNamespaceExist(permissionTest1Namespace)
   476  	}, waitTimeout, pollingInterval).Should(BeTrue(), fmt.Sprintf("Expected to find namespace %s", permissionTest1Namespace))
   477  	Eventually(func() (bool, error) {
   478  		return pkg.DoesNamespaceExist(permissionTest2Namespace)
   479  	}, waitTimeout, pollingInterval).Should(BeTrue(), fmt.Sprintf("Expected to find namespace %s", permissionTest2Namespace))
   480  
   481  	// create a MC config map
   482  	pkg.Log(pkg.Info, "Creating MC config map")
   483  	Eventually(func() error {
   484  		file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_configmap.yaml")
   485  		if err != nil {
   486  			return err
   487  		}
   488  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   489  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   490  
   491  	// create a MC secret
   492  	pkg.Log(pkg.Info, "Creating MC secret")
   493  	Eventually(func() error {
   494  		file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_secret_permissiontest1.yaml")
   495  		if err != nil {
   496  			return err
   497  		}
   498  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   499  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   500  
   501  	// create a OAM Component
   502  	pkg.Log(pkg.Info, "Creating OAM Component")
   503  	Eventually(func() error {
   504  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-oam-component.yaml")
   505  		if err != nil {
   506  			return err
   507  		}
   508  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   509  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   510  
   511  	// create a k8s secret
   512  	pkg.Log(pkg.Info, "Creating k8s secrets")
   513  	Eventually(func() error {
   514  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-secret.yaml")
   515  		if err != nil {
   516  			return err
   517  		}
   518  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   519  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   520  	Eventually(func() error {
   521  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest2-secret.yaml")
   522  		if err != nil {
   523  			return err
   524  		}
   525  		return resource.CreateOrUpdateResourceFromFile(file, t.Logs)
   526  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   527  	metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
   528  }
   529  
   530  // undeployTestResources undeploys the test associated multi cluster resources
   531  func undeployTestResources() {
   532  	pkg.Log(pkg.Info, "Undeploying MC Resources")
   533  
   534  	_ = os.Setenv(k8sutil.EnvVarTestKubeConfig, os.Getenv("ADMIN_KUBECONFIG"))
   535  
   536  	// delete a MC config map
   537  	pkg.Log(pkg.Info, "Deleting MC config map")
   538  	start := time.Now()
   539  	Eventually(func() error {
   540  		file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_configmap.yaml")
   541  		if err != nil {
   542  			return err
   543  		}
   544  		return resource.DeleteResourceFromFile(file, t.Logs)
   545  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   546  
   547  	// delete a MC secret
   548  	pkg.Log(pkg.Info, "Deleting MC secret")
   549  	Eventually(func() error {
   550  		file, err := pkg.FindTestDataFile("testdata/multicluster/multicluster_secret_permissiontest1.yaml")
   551  		if err != nil {
   552  			return err
   553  		}
   554  		return resource.DeleteResourceFromFile(file, t.Logs)
   555  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   556  
   557  	// delete a OAM Component
   558  	pkg.Log(pkg.Info, "Deleting OAM Component")
   559  	Eventually(func() error {
   560  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-oam-component.yaml")
   561  		if err != nil {
   562  			return err
   563  		}
   564  		return resource.DeleteResourceFromFile(file, t.Logs)
   565  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   566  
   567  	// delete k8s secrets
   568  	pkg.Log(pkg.Info, "Deleting k8s secrets")
   569  	Eventually(func() error {
   570  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-secret.yaml")
   571  		if err != nil {
   572  			return err
   573  		}
   574  		return resource.DeleteResourceFromFile(file, t.Logs)
   575  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   576  	Eventually(func() error {
   577  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest2-secret.yaml")
   578  		if err != nil {
   579  			return err
   580  		}
   581  		return resource.DeleteResourceFromFile(file, t.Logs)
   582  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   583  
   584  	// delete the test projects
   585  	pkg.Log(pkg.Info, "Deleting test projects")
   586  	Eventually(func() error {
   587  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest1-verrazzanoproject.yaml")
   588  		if err != nil {
   589  			return err
   590  		}
   591  		return resource.DeleteResourceFromFile(file, t.Logs)
   592  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   593  	Eventually(func() error {
   594  		file, err := pkg.FindTestDataFile("testdata/multicluster/permissiontest2-verrazzanoproject.yaml")
   595  		if err != nil {
   596  			return err
   597  		}
   598  		return resource.DeleteResourceFromFile(file, t.Logs)
   599  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   600  
   601  	// Wait for the project resources to be deleted from the managed cluster
   602  	pkg.Log(pkg.Info, "Wait for the VerrazzanoProject resources to be removed from the managed cluster")
   603  	Eventually(func() (bool, error) {
   604  		return pkg.DoesVerrazzanoProjectExistInCluster(vpTest1, managedKubeconfig)
   605  	}, waitTimeout, pollingInterval).Should(BeFalse(), fmt.Sprintf("Expected VerrazzanoProject %s to be removed from managed cluster", vpTest1))
   606  	Eventually(func() (bool, error) {
   607  		return pkg.DoesVerrazzanoProjectExistInCluster(vpTest2, managedKubeconfig)
   608  	}, waitTimeout, pollingInterval).Should(BeFalse(), fmt.Sprintf("Expected VerrazzanoProject %s to be removed from managed cluster", vpTest2))
   609  
   610  	// delete the test namespaces
   611  	pkg.Log(pkg.Info, fmt.Sprintf("Deleting namespace %s on admin cluster", permissionTest1Namespace))
   612  	Eventually(func() error {
   613  		return pkg.DeleteNamespaceInCluster(permissionTest1Namespace, adminKubeconfig)
   614  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   615  	pkg.Log(pkg.Info, fmt.Sprintf("Deleting namespace %s on managed cluster", permissionTest1Namespace))
   616  	Eventually(func() error {
   617  		return pkg.DeleteNamespaceInCluster(permissionTest1Namespace, managedKubeconfig)
   618  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   619  
   620  	pkg.Log(pkg.Info, fmt.Sprintf("Deleting namespace %s on admin cluster", permissionTest2Namespace))
   621  	Eventually(func() error {
   622  		return pkg.DeleteNamespaceInCluster(permissionTest2Namespace, adminKubeconfig)
   623  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   624  	pkg.Log(pkg.Info, fmt.Sprintf("Deleting namespace %s on managed cluster", permissionTest2Namespace))
   625  	Eventually(func() error {
   626  		return pkg.DeleteNamespaceInCluster(permissionTest2Namespace, managedKubeconfig)
   627  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   628  
   629  	pkg.Log(pkg.Info, fmt.Sprintf("Deleting namespace %s on admin cluster", testNamespace))
   630  	Eventually(func() error {
   631  		return pkg.DeleteNamespaceInCluster(testNamespace, adminKubeconfig)
   632  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   633  	pkg.Log(pkg.Info, fmt.Sprintf("Deleting namespace %s on managed cluster", testNamespace))
   634  	Eventually(func() error {
   635  		return pkg.DeleteNamespaceInCluster(testNamespace, managedKubeconfig)
   636  	}, waitTimeout, pollingInterval).ShouldNot(HaveOccurred())
   637  	metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
   638  }
   639  
   640  // findSecret finds the secret based on name and namespace
   641  func findSecret(namespace, name string) (bool, error) {
   642  	clustersClient, err := getClustersClient()
   643  	if err != nil {
   644  		return false, err
   645  	}
   646  	secretList := v1.SecretList{}
   647  	err = clustersClient.List(context.TODO(), &secretList, &client.ListOptions{Namespace: namespace})
   648  	// Handle the case of forbidden as secret not found
   649  	if err != nil && errors.IsForbidden(err) {
   650  		return false, nil
   651  	}
   652  	if err != nil {
   653  		pkg.Log(pkg.Error, fmt.Sprintf("Failed to list secrets with error: %v", err))
   654  		return false, err
   655  	}
   656  	for _, item := range secretList.Items {
   657  		if item.Name == name && item.Namespace == namespace {
   658  			return true, nil
   659  		}
   660  	}
   661  	return false, nil
   662  }
   663  
   664  // findConfigMap finds the config map based on name and namespace
   665  func findConfigMap(namespace, name string) (bool, error) {
   666  	clustersClient, err := getClustersClient()
   667  	if err != nil {
   668  		return false, err
   669  	}
   670  	configmapList := v1.ConfigMapList{}
   671  	err = clustersClient.List(context.TODO(), &configmapList, &client.ListOptions{Namespace: namespace})
   672  	if err != nil {
   673  		pkg.Log(pkg.Error, fmt.Sprintf("Failed to list config maps with error: %v", err))
   674  		return false, err
   675  	}
   676  	for _, item := range configmapList.Items {
   677  		if item.Name == name && item.Namespace == namespace {
   678  			return true, nil
   679  		}
   680  	}
   681  	return false, nil
   682  }
   683  
   684  // listResource returns a list of resources based on the object type and namespace
   685  func listResource(namespace string, objectList client.ObjectList) error {
   686  	clustersClient, err := getClustersClient()
   687  	if err != nil {
   688  		return err
   689  	}
   690  	return clustersClient.List(context.TODO(), objectList, &client.ListOptions{Namespace: namespace})
   691  }
   692  
   693  // getMultiClusterResource returns a multi cluster resource based the provided multi cluster object's type and namespace
   694  func getMultiClusterResource(namespace, name string, object client.Object) error {
   695  	clustersClient, err := getClustersClient()
   696  	if err != nil {
   697  		return err
   698  	}
   699  	return clustersClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, object)
   700  }
   701  
   702  // findMultiClusterConfigMap returns true if the config map is found based on name and namespace, false otherwise
   703  func findMultiClusterConfigMap(namespace, name string) (bool, error) {
   704  	clustersClient, err := getClustersClient()
   705  	if err != nil {
   706  		return false, err
   707  	}
   708  	configmapList := clustersv1alpha1.MultiClusterConfigMapList{}
   709  	err = clustersClient.List(context.TODO(), &configmapList, &client.ListOptions{Namespace: namespace})
   710  	if err != nil {
   711  		pkg.Log(pkg.Error, fmt.Sprintf("Failed to list multi cluster configmaps with error: %v", err))
   712  		return false, err
   713  	}
   714  	for _, item := range configmapList.Items {
   715  		if item.Name == name && item.Namespace == namespace {
   716  			return true, nil
   717  		}
   718  	}
   719  	return false, nil
   720  }
   721  
   722  // findMultiClusterSecret returns true if the secret is found based on name and namespace, false otherwise
   723  func findMultiClusterSecret(namespace, name string) (bool, error) {
   724  	clustersClient, err := getClustersClient()
   725  	if err != nil {
   726  		return false, err
   727  	}
   728  	secretList := clustersv1alpha1.MultiClusterSecretList{}
   729  	err = clustersClient.List(context.TODO(), &secretList, &client.ListOptions{Namespace: namespace})
   730  	if err != nil {
   731  		pkg.Log(pkg.Error, fmt.Sprintf("Failed to list multi cluster secrets with error: %v", err))
   732  		return false, err
   733  	}
   734  	for _, item := range secretList.Items {
   735  		if item.Name == name && item.Namespace == namespace {
   736  			return true, nil
   737  		}
   738  	}
   739  	return false, nil
   740  }
   741  
   742  // findComponent returns true if the OAM component is found based on name and namespace, false otherwise
   743  func findOAMComponent(namespace, name string) (bool, error) {
   744  	clustersClient, err := getClustersClient()
   745  	if err != nil {
   746  		return false, err
   747  	}
   748  	component := &oamv1alpha2.Component{}
   749  	err = clustersClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, component)
   750  	if err != nil {
   751  		return false, err
   752  	}
   753  	return true, nil
   754  }
   755  
   756  // getClustersClient returns a k8s client
   757  func getClustersClient() (client.Client, error) {
   758  	config, err := clientcmd.BuildConfigFromFlags("", os.Getenv(k8sutil.EnvVarTestKubeConfig))
   759  	if err != nil {
   760  		pkg.Log(pkg.Error, fmt.Sprintf("failed to build config from %s with error: %v", os.Getenv(k8sutil.EnvVarTestKubeConfig), err))
   761  		return nil, err
   762  	}
   763  
   764  	scheme := runtime.NewScheme()
   765  	_ = clustersv1alpha1.AddToScheme(scheme)
   766  	_ = v1alpha12.AddToScheme(scheme)
   767  	_ = v1alpha1.AddToScheme(scheme)
   768  	_ = v1.AddToScheme(scheme)
   769  	_ = oamv1alpha2.SchemeBuilder.AddToScheme(scheme)
   770  
   771  	clustersClient, err := client.New(config, client.Options{Scheme: scheme})
   772  	if err != nil {
   773  		pkg.Log(pkg.Error, fmt.Sprintf("Failed to get clusters client with error: %v", err))
   774  		return nil, err
   775  	}
   776  	return clustersClient, nil
   777  }
   778  
   779  // isStatusAsExpected checks whehter the provided inputs align with the provided status
   780  func isStatusAsExpected(status clustersv1alpha1.MultiClusterResourceStatus,
   781  	expectedConditionType clustersv1alpha1.ConditionType,
   782  	conditionMsgContains string,
   783  	expectedClusterState clustersv1alpha1.StateType,
   784  	expectedClusterName string) bool {
   785  	matchingConditionCount := 0
   786  	matchingClusterStatusCount := 0
   787  	for _, condition := range status.Conditions {
   788  		if condition.Type == expectedConditionType && strings.Contains(condition.Message, conditionMsgContains) {
   789  			matchingConditionCount++
   790  		}
   791  	}
   792  	for _, clusterStatus := range status.Clusters {
   793  		if clusterStatus.State == expectedClusterState &&
   794  			clusterStatus.Name == expectedClusterName &&
   795  			clusterStatus.LastUpdateTime != "" {
   796  			matchingClusterStatusCount++
   797  		}
   798  	}
   799  	return matchingConditionCount >= 1 && matchingClusterStatusCount == 1
   800  }
   801  
   802  // getUserKubeconfigForManagedCluster calls the Rancher API and downloads a kubeconfig configured to access
   803  // the managed cluster using the Verrazzano cluster user. That user has a very limited set of roles (the roles
   804  // needed to push managed cluster resources). This function returns the file path to the kubeconfig file on success.
   805  func getUserKubeconfigForManagedCluster(httpClient *retryablehttp.Client) (string, error) {
   806  	// get the Rancher cluster id from the VMC status
   807  	client, err := pkg.GetClusterOperatorClientset(adminKubeconfig)
   808  	if err != nil {
   809  		return "", err
   810  	}
   811  	vmc, err := client.ClustersV1alpha1().VerrazzanoManagedClusters(constants.VerrazzanoMultiClusterNamespace).Get(context.TODO(), managedClusterName, metav1.GetOptions{})
   812  	if err != nil {
   813  		return "", err
   814  	}
   815  	if vmc.Status.RancherRegistration.ClusterID == "" {
   816  		return "", fmt.Errorf("Rancher status cluster id is empty")
   817  	}
   818  
   819  	// get the Verrazzano cluster user password from the secret and create a Rancher config with a bearer token for the user
   820  	secret, err := pkg.GetSecretInCluster(constants.VerrazzanoMultiClusterNamespace, constants.VerrazzanoClusterRancherName, adminKubeconfig)
   821  	if err != nil {
   822  		return "", err
   823  	}
   824  	config, err := pkg.CreateNewRancherConfigForUser(t.Logs, adminKubeconfig, constants.VerrazzanoClusterRancherUsername, string(secret.Data["password"]))
   825  	if err != nil {
   826  		return "", err
   827  	}
   828  
   829  	// get the managed cluster kubeconfig configured for the user from Rancher
   830  	kubeconfig, err := pkg.GetClusterKubeconfig(t.Logs, httpClient, config, vmc.Status.RancherRegistration.ClusterID)
   831  	if err != nil {
   832  		return "", err
   833  	}
   834  
   835  	// write the kubeconfig contents to a temp file
   836  	tmpFile, err := os.CreateTemp("", "")
   837  	if err != nil {
   838  		return "", err
   839  	}
   840  	defer tmpFile.Close()
   841  	_, err = tmpFile.WriteString(kubeconfig)
   842  	if err != nil {
   843  		return "", err
   844  	}
   845  
   846  	return tmpFile.Name(), nil
   847  }