github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/rancher.go (about)

     1  // Copyright (c) 2022, 2023, 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  
     4  package pkg
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	urlpkg "net/url"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/hashicorp/go-retryablehttp"
    17  	"github.com/onsi/gomega"
    18  	"github.com/verrazzano/verrazzano/pkg/constants"
    19  	"github.com/verrazzano/verrazzano/pkg/httputil"
    20  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    21  	"github.com/verrazzano/verrazzano/pkg/rancherutil"
    22  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common"
    23  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/rancher"
    24  	"go.uber.org/zap"
    25  	corev1 "k8s.io/api/core/v1"
    26  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  )
    29  
    30  type Payload struct {
    31  	ClusterID string `json:"clusterID"`
    32  	TTL       int    `json:"ttl"`
    33  }
    34  
    35  type TokenPostResponse struct {
    36  	Token   string `json:"token"`
    37  	Created string `json:"created"`
    38  }
    39  
    40  type ListOfTokenOutputFromRancher struct {
    41  	Data []struct {
    42  		ClusterID string `json:"clusterId"`
    43  		Name      string `json:"name"`
    44  	} `json:"data"`
    45  }
    46  
    47  func EventuallyGetURLForIngress(log *zap.SugaredLogger, api *APIEndpoint, namespace string, name string, scheme string) string {
    48  	ingressHost := EventuallyGetIngressHost(log, api, namespace, name)
    49  	gomega.Expect(ingressHost).ToNot(gomega.BeEmpty())
    50  	return fmt.Sprintf("%s://%s", scheme, ingressHost)
    51  }
    52  
    53  func EventuallyGetIngressHost(log *zap.SugaredLogger, api *APIEndpoint, namespace string, name string) string {
    54  	var ingressHost string
    55  	gomega.Eventually(func() error {
    56  		ingress, err := api.GetIngress(namespace, name)
    57  		if err != nil {
    58  			return err
    59  		}
    60  		if len(ingress.Spec.Rules) == 0 {
    61  			return fmt.Errorf("no rules found in ingress %s/%s", namespace, name)
    62  		}
    63  		ingressHost = ingress.Spec.Rules[0].Host
    64  		log.Info(fmt.Sprintf("Found ingress host: %s", ingressHost))
    65  		return nil
    66  	}, waitTimeout, pollingInterval).Should(gomega.BeNil())
    67  	return ingressHost
    68  }
    69  
    70  func GetURLForIngress(log *zap.SugaredLogger, api *APIEndpoint, namespace string, name string, scheme string) (string, error) {
    71  	ingress, err := api.GetIngress(namespace, name)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  	ingressURL := fmt.Sprintf("%s://%s", scheme, ingress.Spec.Rules[0].Host)
    76  	log.Info(fmt.Sprintf("Found ingress URL: %s", ingressURL))
    77  	return ingressURL, err
    78  }
    79  
    80  func eventuallyGetRancherAdminPassword(log *zap.SugaredLogger) (string, error) {
    81  	var err error
    82  	var secret *corev1.Secret
    83  	gomega.Eventually(func() error {
    84  		secret, err = GetSecret("cattle-system", "rancher-admin-secret")
    85  		if err != nil {
    86  			log.Error(fmt.Sprintf("Error getting rancher-admin-secret, retrying: %v", err))
    87  		}
    88  		return err
    89  	}, waitTimeout, pollingInterval).Should(gomega.BeNil())
    90  
    91  	if secret == nil {
    92  		return "", fmt.Errorf("Unable to get rancher admin secret")
    93  	}
    94  
    95  	var rancherAdminPassword []byte
    96  	var ok bool
    97  	if rancherAdminPassword, ok = secret.Data["password"]; !ok {
    98  		return "", fmt.Errorf("Error getting rancher admin credentials")
    99  	}
   100  
   101  	return string(rancherAdminPassword), nil
   102  }
   103  
   104  func GetRancherAdminToken(log *zap.SugaredLogger, httpClient *retryablehttp.Client, rancherURL string) string {
   105  	rancherAdminPassword, err := eventuallyGetRancherAdminPassword(log)
   106  	if err != nil {
   107  		log.Error(fmt.Sprintf("Error getting rancher admin password: %v", err))
   108  		return ""
   109  	}
   110  
   111  	token, err := getRancherUserToken(log, httpClient, rancherURL, "admin", string(rancherAdminPassword))
   112  	if err != nil {
   113  		log.Error(fmt.Sprintf("Error getting user token from rancher: %v", err))
   114  		return ""
   115  	}
   116  
   117  	return token
   118  }
   119  
   120  func getRancherUserToken(log *zap.SugaredLogger, httpClient *retryablehttp.Client, rancherURL string, username string, password string) (string, error) {
   121  	rancherLoginURL := fmt.Sprintf("%s/%s", rancherURL, "v3-public/localProviders/local?action=login")
   122  	payload := `{"Username": "` + username + `", "Password": "` + password + `"}`
   123  	response, err := httpClient.Post(rancherLoginURL, "application/json", strings.NewReader(payload))
   124  	if err != nil {
   125  		log.Error(fmt.Sprintf("Error getting rancher admin token: %v", err))
   126  		return "", err
   127  	}
   128  
   129  	err = httputil.ValidateResponseCode(response, http.StatusCreated)
   130  	if err != nil {
   131  		log.Errorf("Invalid response code when fetching Rancher token: %v", err)
   132  		return "", err
   133  	}
   134  
   135  	defer response.Body.Close()
   136  
   137  	// extract the response body
   138  	body, err := io.ReadAll(response.Body)
   139  	if err != nil {
   140  		log.Errorf("Failed to read Rancher token response: %v", err)
   141  		return "", err
   142  	}
   143  
   144  	token, err := httputil.ExtractFieldFromResponseBodyOrReturnError(string(body), "token", "unable to find token in Rancher response")
   145  	if err != nil {
   146  		log.Errorf("Failed to extract token from Rancher response: %v", err)
   147  		return "", err
   148  	}
   149  
   150  	return token, nil
   151  }
   152  
   153  // This function adds an access token to Rancher gven that a ttl and clusterID string is provided
   154  func AddAccessTokenToRancherForLoggedInUser(log *zap.SugaredLogger, adminKubeConfig, managedClusterName, usernameForRancher, ttl string) (string, error) {
   155  	responseBody, err := ExecutePostRequestToAddAToken(log, adminKubeConfig, managedClusterName, usernameForRancher, ttl)
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  	var tokenPostResponse TokenPostResponse
   160  	err = json.Unmarshal(responseBody, &tokenPostResponse)
   161  	if err != nil {
   162  		return "", err
   163  	}
   164  
   165  	return tokenPostResponse.Created, nil
   166  }
   167  
   168  // This function returns the list of token names that correspond to the cluster ID and this user before when this is called
   169  // If no error occurs, this means that these tokens were found and deleted in Rancher
   170  func GetAndDeleteTokenNamesForLoggedInUserBasedOnClusterID(log *zap.SugaredLogger, adminKubeConfig, managedClusterName, usernameForRancher string) error {
   171  	responseBody, err := ExecuteGetRequestToReturnAllTokens(log, adminKubeConfig, managedClusterName, usernameForRancher)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	var listOfTokenOutputFromRancher = ListOfTokenOutputFromRancher{}
   176  	err = json.Unmarshal(responseBody, &listOfTokenOutputFromRancher)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	listOfTokens := listOfTokenOutputFromRancher.Data
   181  
   182  	clusterID, err := getClusterIDForManagedCluster(adminKubeConfig, managedClusterName)
   183  
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	for _, token := range listOfTokens {
   189  		//Check that it is not the same name as the user access token and it has the same cluster ID
   190  		if token.ClusterID != clusterID {
   191  			continue
   192  		}
   193  		err = ExecuteDeleteRequestForToken(log, adminKubeConfig, managedClusterName, usernameForRancher, token.Name)
   194  		if err != nil {
   195  			return err
   196  		}
   197  	}
   198  
   199  	return nil
   200  
   201  }
   202  
   203  // VerifyRancherAccess verifies that Rancher is accessible.
   204  func VerifyRancherAccess(log *zap.SugaredLogger) error {
   205  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   206  	if err != nil {
   207  		log.Error(fmt.Sprintf("Error getting kubeconfig: %v", err))
   208  		return err
   209  	}
   210  
   211  	api := EventuallyGetAPIEndpoint(kubeconfigPath)
   212  	rancherURL := EventuallyGetURLForIngress(log, api, "cattle-system", "rancher", "https")
   213  	httpClient := EventuallyVerrazzanoRetryableHTTPClient()
   214  	var httpResponse *HTTPResponse
   215  
   216  	gomega.Eventually(func() (*HTTPResponse, error) {
   217  		httpResponse, err = GetWebPageWithClient(httpClient, rancherURL, "")
   218  		return httpResponse, err
   219  	}, waitTimeout, pollingInterval).Should(HasStatus(http.StatusOK))
   220  
   221  	gomega.Expect(CheckNoServerHeader(httpResponse)).To(gomega.BeTrue(), "Found unexpected server header in response")
   222  	return nil
   223  }
   224  
   225  // VerifyRancherKeycloakAuthConfig verifies that Rancher/Keycloak AuthConfig is correctly populated
   226  func VerifyRancherKeycloakAuthConfig(log *zap.SugaredLogger) error {
   227  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   228  	if err != nil {
   229  		log.Error(fmt.Sprintf("Error getting kubeconfig: %v", err))
   230  		return err
   231  	}
   232  
   233  	log.Info("Verify Keycloak AuthConfig")
   234  
   235  	gomega.Eventually(func() (bool, error) {
   236  		api, err := GetAPIEndpoint(kubeconfigPath)
   237  		if err != nil {
   238  			log.Error(fmt.Sprintf("Error getting API endpoint: %v", err))
   239  			return false, err
   240  		}
   241  		keycloakURL, err := GetURLForIngress(log, api, "keycloak", "keycloak", "https")
   242  		if err != nil {
   243  			log.Error(fmt.Sprintf("Error getting API endpoint: %v", err))
   244  			return false, err
   245  		}
   246  		rancherURL, err := GetURLForIngress(log, api, "cattle-system", "rancher", "https")
   247  		if err != nil {
   248  			return false, err
   249  		}
   250  		k8sClient, err := GetDynamicClientInCluster(kubeconfigPath)
   251  		if err != nil {
   252  			log.Error(fmt.Sprintf("Error getting dynamic client: %v", err))
   253  			return false, err
   254  		}
   255  
   256  		authConfigData, err := k8sClient.Resource(GvkToGvr(common.GVKAuthConfig)).Get(context.Background(), common.AuthConfigKeycloak, v1.GetOptions{})
   257  		if err != nil {
   258  			log.Error(fmt.Sprintf("error getting keycloak oidc authConfig: %v", err))
   259  			return false, err
   260  		}
   261  
   262  		authConfigAttributes := authConfigData.UnstructuredContent()
   263  		if err = verifyAuthConfigAttribute(rancher.AuthConfigKeycloakAttributeAccessMode, authConfigAttributes[rancher.AuthConfigKeycloakAttributeAccessMode].(string), rancher.AuthConfigKeycloakAccessMode); err != nil {
   264  			log.Error(err)
   265  			return false, err
   266  		}
   267  
   268  		if err = verifyAuthConfigAttribute(rancher.AuthConfigKeycloakAttributeClientID, authConfigAttributes[rancher.AuthConfigKeycloakAttributeClientID].(string), rancher.AuthConfigKeycloakClientIDRancher); err != nil {
   269  			log.Error(err)
   270  			return false, err
   271  		}
   272  
   273  		if err = verifyAuthConfigAttribute(rancher.AuthConfigKeycloakAttributeGroupSearchEnabled, authConfigAttributes[rancher.AuthConfigKeycloakAttributeGroupSearchEnabled].(bool), true); err != nil {
   274  			return false, err
   275  		}
   276  
   277  		if err = verifyAuthConfigAttribute(rancher.AuthConfigKeycloakAttributeAuthEndpoint, authConfigAttributes[rancher.AuthConfigKeycloakAttributeAuthEndpoint].(string), keycloakURL+rancher.AuthConfigKeycloakURLPathAuthEndPoint); err != nil {
   278  			log.Error(err)
   279  			return false, err
   280  		}
   281  
   282  		if err = verifyAuthConfigAttribute(rancher.AuthConfigKeycloakAttributeRancherURL, authConfigAttributes[rancher.AuthConfigKeycloakAttributeRancherURL].(string), rancherURL+rancher.AuthConfigKeycloakURLPathVerifyAuth); err != nil {
   283  			log.Error(err)
   284  			return false, err
   285  		}
   286  
   287  		authConfigClientSecret := authConfigAttributes[common.AuthConfigKeycloakAttributeClientSecret].(string)
   288  		if authConfigClientSecret == "" {
   289  			err = fmt.Errorf("keycloak auth config attribute %s not correctly configured, value is empty", common.AuthConfigKeycloakAttributeClientSecret)
   290  			log.Error(err)
   291  			return false, err
   292  		}
   293  
   294  		return true, nil
   295  	}, waitTimeout, pollingInterval).Should(gomega.Equal(true), "keycloak oidc authconfig not configured correctly")
   296  	return nil
   297  }
   298  
   299  // GvkToGvr converts a GroupVersionKind to corresponding GroupVersionResource
   300  func GvkToGvr(gvk schema.GroupVersionKind) schema.GroupVersionResource {
   301  	resource := strings.ToLower(gvk.Kind)
   302  	if strings.HasSuffix(resource, "s") {
   303  		resource = resource + "es"
   304  	} else {
   305  		resource = resource + "s"
   306  	}
   307  
   308  	return schema.GroupVersionResource{Group: gvk.Group,
   309  		Version:  gvk.Version,
   310  		Resource: resource,
   311  	}
   312  }
   313  
   314  func verifyAuthConfigAttribute(name string, actual interface{}, expected interface{}) error {
   315  	if expected != actual {
   316  		return fmt.Errorf("keycloak auth config attribute %s not correctly configured, expected %v, actual %v", name, expected, actual)
   317  	}
   318  	return nil
   319  }
   320  
   321  func EventuallyGetRancherHost(log *zap.SugaredLogger, api *APIEndpoint) (string, error) {
   322  	rancherHost := EventuallyGetIngressHost(log, api, rancher.ComponentNamespace, common.RancherName)
   323  	if rancherHost == "" {
   324  		return "", fmt.Errorf("got empty Rancher ingress host")
   325  	}
   326  	return rancherHost, nil
   327  }
   328  
   329  func CreateNewRancherConfig(log *zap.SugaredLogger, kubeconfigPath string) (*rancherutil.RancherConfig, error) {
   330  	rancherAdminPassword, err := eventuallyGetRancherAdminPassword(log)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	return CreateNewRancherConfigForUser(log, kubeconfigPath, "admin", rancherAdminPassword)
   335  }
   336  
   337  func CreateNewRancherConfigForUser(log *zap.SugaredLogger, kubeconfigPath string, username string, password string) (*rancherutil.RancherConfig, error) {
   338  	apiEndpoint := EventuallyGetAPIEndpoint(kubeconfigPath)
   339  	rancherHost, err := EventuallyGetRancherHost(log, apiEndpoint)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	rancherURL := fmt.Sprintf("https://%s", rancherHost)
   344  	caCert, err := GetCACertFromSecret(common.RancherIngressCAName, constants.RancherSystemNamespace, "ca.crt", kubeconfigPath)
   345  	if err != nil {
   346  		return nil, fmt.Errorf("failed to get caCert: %v", err)
   347  	}
   348  
   349  	// the tls-ca secret is optional, and contains the private CA bundle configured for Rancher
   350  	additionalCA, _ := GetCACertFromSecret(constants.RancherTLSCA, constants.RancherSystemNamespace, constants.RancherTLSCAKey, kubeconfigPath)
   351  
   352  	httpClient, err := GetVerrazzanoHTTPClient(kubeconfigPath)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  	token, err := getRancherUserToken(log, httpClient, rancherURL, username, password)
   357  	if err != nil {
   358  		return nil, fmt.Errorf("failed to get user token from Rancher: %v", err)
   359  	}
   360  
   361  	rc := rancherutil.RancherConfig{
   362  		// populate Rancher config from the functions available in this file,adding as necessary
   363  		BaseURL:                  rancherURL,
   364  		Host:                     rancherHost,
   365  		APIAccessToken:           token,
   366  		CertificateAuthorityData: caCert,
   367  		AdditionalCA:             additionalCA,
   368  	}
   369  	return &rc, nil
   370  }
   371  
   372  func GetClusterKubeconfig(log *zap.SugaredLogger, httpClient *retryablehttp.Client, rc *rancherutil.RancherConfig, clusterID string) (string, error) {
   373  	reqURL := rc.BaseURL + "/v3/clusters/" + clusterID + "?action=generateKubeconfig"
   374  	req, err := retryablehttp.NewRequest("POST", reqURL, nil)
   375  	if err != nil {
   376  		return "", err
   377  	}
   378  	req.Header.Set("Authorization", "Bearer "+rc.APIAccessToken)
   379  
   380  	response, err := httpClient.Do(req)
   381  	if err != nil {
   382  		log.Error(fmt.Sprintf("Error getting managed cluster kubeconfig: %v", err))
   383  		return "", err
   384  	}
   385  
   386  	err = httputil.ValidateResponseCode(response, http.StatusOK)
   387  	if err != nil {
   388  		log.Errorf("Invalid response code when fetching cluster kubeconfig: %v", err)
   389  		return "", err
   390  	}
   391  
   392  	defer response.Body.Close()
   393  
   394  	// extract the response body
   395  	responseBody, err := io.ReadAll(response.Body)
   396  	if err != nil {
   397  		log.Errorf("Failed to read Rancher kubeconfig response: %v", err)
   398  		return "", err
   399  	}
   400  
   401  	return httputil.ExtractFieldFromResponseBodyOrReturnError(string(responseBody), "config", "")
   402  }
   403  
   404  // This function is a wrapper function that executes a GET Request to return all tokens in Rancher for a given user
   405  func ExecuteGetRequestToReturnAllTokens(log *zap.SugaredLogger, adminKubeconfig, managedClusterName, usernameForRancher string) ([]byte, error) {
   406  	getReq, err := retryablehttp.NewRequest("GET", "", nil)
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  	getReq.Header = map[string][]string{"Content-Type": {"application/json"}, "Accept": {"application/json"}}
   411  	return sendTokenRequestToRancher(log, adminKubeconfig, managedClusterName, usernameForRancher, getReq, http.StatusOK, "/v3/tokens")
   412  }
   413  
   414  // This function is a wrapper function that executes a POST Request to add a token in Rancher for a given user
   415  func ExecutePostRequestToAddAToken(log *zap.SugaredLogger, adminKubeconfig, managedClusterName, usernameForRancher, ttl string) ([]byte, error) {
   416  	clusterID, err := getClusterIDForManagedCluster(adminKubeconfig, managedClusterName)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	val, _ := strconv.Atoi(ttl)
   421  	payload := &Payload{
   422  		ClusterID: clusterID,
   423  		TTL:       val * 60000,
   424  	}
   425  	data, err := json.Marshal(payload)
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  	postReq, err := retryablehttp.NewRequest("POST", "", data)
   430  	if err != nil {
   431  		return nil, err
   432  	}
   433  	postReq.Header = map[string][]string{"Content-Type": {"application/json"}}
   434  	return sendTokenRequestToRancher(log, adminKubeconfig, managedClusterName, usernameForRancher, postReq, http.StatusCreated, "/v3/tokens")
   435  }
   436  
   437  // This function is a wrapper function to delete a given token for a specified user in Rancher
   438  func ExecuteDeleteRequestForToken(log *zap.SugaredLogger, adminKubeconfig, managedClusterName, usernameForRancher, tokenName string) error {
   439  	deleteReq, err := retryablehttp.NewRequest("DELETE", "", nil)
   440  	if err != nil {
   441  		return err
   442  	}
   443  	deleteReq.Header = map[string][]string{"Accept": {"application/json"}}
   444  	_, err = sendTokenRequestToRancher(log, adminKubeconfig, managedClusterName, usernameForRancher, deleteReq, http.StatusNoContent, "/v3/tokens/"+tokenName)
   445  	return err
   446  }
   447  
   448  // This function is a helper function that sends a Token Request to Rancher for a specified user
   449  // This function expects a retryable HTTP Request object
   450  func sendTokenRequestToRancher(log *zap.SugaredLogger, adminKubeconfig, managedClusterName, usernameForRancher string, requestObject *retryablehttp.Request, expectedReturnCode int, requestPath string) ([]byte, error) {
   451  	httpClient, APIAccessToken, err := getRequiredInfoToPreformTokenOperationsInRancherForArgoCD(log, adminKubeconfig, managedClusterName, usernameForRancher)
   452  	if err != nil {
   453  		return nil, err
   454  	}
   455  	api, err := GetAPIEndpoint(adminKubeconfig)
   456  	if err != nil {
   457  		log.Errorf("API Endpoint not successfully received based on KubeConfig Path")
   458  		return nil, err
   459  	}
   460  	rancherURL, err := GetURLForIngress(log, api, "cattle-system", "rancher", "https")
   461  	if err != nil {
   462  		log.Errorf("URL For Rancher not successfully found")
   463  		return nil, err
   464  	}
   465  	reqURL := rancherURL + requestPath
   466  	URLForRequest, err := urlpkg.Parse(reqURL)
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  	requestObject.URL = URLForRequest
   471  	requestObject.Header["Authorization"] = []string{"Bearer " + APIAccessToken}
   472  	response, err := httpClient.Do(requestObject)
   473  	if err != nil {
   474  		return nil, err
   475  	}
   476  	responseBody, err := io.ReadAll(response.Body)
   477  	if err != nil {
   478  		return nil, err
   479  	}
   480  	err = httputil.ValidateResponseCode(response, expectedReturnCode)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  	return responseBody, err
   485  
   486  }
   487  
   488  // This function gets the necessary information required to access the token API resources in Rancher for ArgoCD
   489  func getRequiredInfoToPreformTokenOperationsInRancherForArgoCD(log *zap.SugaredLogger, adminKubeconfig, managedClusterName, argoCDUsernameForRancher string) (httpClient *retryablehttp.Client, APIAccessToken string, err error) {
   490  	argoCDPasswordForRancher, err := RetrieveArgoCDPassword("verrazzano-mc", "verrazzano-argocd-secret")
   491  	if err != nil {
   492  		return nil, "", err
   493  	}
   494  	rancherConfigForArgoCD, err := CreateNewRancherConfigForUser(log, adminKubeconfig, argoCDUsernameForRancher, argoCDPasswordForRancher)
   495  	if err != nil {
   496  		Log(Error, "Error occurred when created a Rancher Config for ArgoCD")
   497  		return nil, "", err
   498  	}
   499  	httpClientForRancher, err := GetVerrazzanoHTTPClient(adminKubeconfig)
   500  	if err != nil {
   501  		Log(Error, "Error getting the Verrazzano http client")
   502  		return nil, "", err
   503  	}
   504  	return httpClientForRancher, rancherConfigForArgoCD.APIAccessToken, nil
   505  }
   506  
   507  func getClusterIDForManagedCluster(adminKubeConfig, managedClusterName string) (string, error) {
   508  	client, err := GetClusterOperatorClientset(adminKubeConfig)
   509  	if err != nil {
   510  		Log(Error, "Error creating the client set used by the cluster operator")
   511  		return "", err
   512  	}
   513  	managedCluster, err := client.ClustersV1alpha1().VerrazzanoManagedClusters(constants.VerrazzanoMultiClusterNamespace).Get(context.TODO(), managedClusterName, v1.GetOptions{})
   514  	if err != nil {
   515  		Log(Error, "Error getting the current managed cluster resource")
   516  		return "", err
   517  	}
   518  	clusterID := managedCluster.Status.RancherRegistration.ClusterID
   519  	if clusterID == "" {
   520  		Log(Error, "The managed cluster does not have a clusterID value")
   521  		err := fmt.Errorf("ClusterID value is not yet populated for the managed cluster")
   522  		return "", err
   523  	}
   524  	return clusterID, nil
   525  
   526  }