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 }