github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/verify-install/keycloak/keycloak_test.go (about)

     1  // Copyright (c) 2021, 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 keycloak
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"os/exec"
    10  	"path"
    11  	"strings"
    12  	"time"
    13  
    14  	. "github.com/onsi/ginkgo/v2"
    15  	. "github.com/onsi/gomega"
    16  	"github.com/verrazzano/verrazzano/pkg/constants"
    17  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    18  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    19  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    20  	corev1 "k8s.io/api/core/v1"
    21  	"k8s.io/apimachinery/pkg/api/errors"
    22  )
    23  
    24  const (
    25  	waitTimeout       = 10 * time.Minute
    26  	pollingInterval   = 30 * time.Second
    27  	keycloakNamespace = "keycloak"
    28  	vzUser            = "verrazzano"
    29  	realmMgmt         = "realm-management"
    30  	viewUsersRole     = "view-users"
    31  	osdURI            = "osd.vmi.system."
    32  	osURI             = "opensearch.vmi.system."
    33  	grafanaURI        = "grafana.vmi.system."
    34  	prometheusURI     = "prometheus.vmi.system."
    35  	kialiURI          = "kiali.vmi.system."
    36  	verrazzanoURI     = "verrazzano."
    37  	rancherURI        = "rancher."
    38  	kcAdminScript     = "/opt/keycloak/bin/kcadm.sh"
    39  	argocdURI         = "argocd."
    40  	alertmanagerURI   = "alertmanager."
    41  )
    42  
    43  // KeycloakClients represents an array of clients currently configured in Keycloak
    44  type KeycloakClients []struct {
    45  	ID       string `json:"id"`
    46  	ClientID string `json:"clientId"`
    47  }
    48  
    49  // keycloakRoles represents an array of role names configured in Keycloak
    50  type keycloakRoles []struct {
    51  	Name string `json:"name"`
    52  }
    53  
    54  type Client struct {
    55  	ID                        string   `json:"id"`
    56  	ClientID                  string   `json:"clientId"`
    57  	SurrogateAuthRequired     bool     `json:"surrogateAuthRequired"`
    58  	Enabled                   bool     `json:"enabled"`
    59  	AlwaysDisplayInConsole    bool     `json:"alwaysDisplayInConsole"`
    60  	ClientAuthenticatorType   string   `json:"clientAuthenticatorType"`
    61  	RedirectUris              []string `json:"redirectUris"`
    62  	WebOrigins                []string `json:"webOrigins"`
    63  	NotBefore                 int      `json:"notBefore"`
    64  	BearerOnly                bool     `json:"bearerOnly"`
    65  	ConsentRequired           bool     `json:"consentRequired"`
    66  	StandardFlowEnabled       bool     `json:"standardFlowEnabled"`
    67  	ImplicitFlowEnabled       bool     `json:"implicitFlowEnabled"`
    68  	DirectAccessGrantsEnabled bool     `json:"directAccessGrantsEnabled"`
    69  	ServiceAccountsEnabled    bool     `json:"serviceAccountsEnabled"`
    70  	PublicClient              bool     `json:"publicClient"`
    71  	FrontchannelLogout        bool     `json:"frontchannelLogout"`
    72  	Protocol                  string   `json:"protocol"`
    73  	Attributes                struct {
    74  		SamlAssertionSignature                string `json:"saml.assertion.signature"`
    75  		SamlForcePostBinding                  string `json:"saml.force.post.binding"`
    76  		SamlMultivaluedRoles                  string `json:"saml.multivalued.roles"`
    77  		SamlEncrypt                           string `json:"saml.encrypt"`
    78  		SamlServerSignature                   string `json:"saml.server.signature"`
    79  		SamlServerSignatureKeyinfoExt         string `json:"saml.server.signature.keyinfo.ext"`
    80  		ExcludeSessionStateFromAuthResponse   string `json:"exclude.session.state.from.auth.response"`
    81  		SamlForceNameIDFormat                 string `json:"saml_force_name_id_format"`
    82  		SamlClientSignature                   string `json:"saml.client.signature"`
    83  		TLSClientCertificateBoundAccessTokens string `json:"tls.client.certificate.bound.access.tokens"`
    84  		SamlAuthnstatement                    string `json:"saml.authnstatement"`
    85  		DisplayOnConsentScreen                string `json:"display.on.consent.screen"`
    86  		PkceCodeChallengeMethod               string `json:"pkce.code.challenge.method"`
    87  		SamlOnetimeuseCondition               string `json:"saml.onetimeuse.condition"`
    88  	} `json:"attributes"`
    89  	AuthenticationFlowBindingOverrides struct {
    90  	} `json:"authenticationFlowBindingOverrides"`
    91  	FullScopeAllowed          bool `json:"fullScopeAllowed"`
    92  	NodeReRegistrationTimeout int  `json:"nodeReRegistrationTimeout"`
    93  	ProtocolMappers           []struct {
    94  		ID              string `json:"id"`
    95  		Name            string `json:"name"`
    96  		Protocol        string `json:"protocol"`
    97  		ProtocolMapper  string `json:"protocolMapper"`
    98  		ConsentRequired bool   `json:"consentRequired"`
    99  		Config          struct {
   100  			Multivalued        string `json:"multivalued"`
   101  			UserinfoTokenClaim string `json:"userinfo.token.claim"`
   102  			UserAttribute      string `json:"user.attribute"`
   103  			IDTokenClaim       string `json:"id.token.claim"`
   104  			AccessTokenClaim   string `json:"access.token.claim"`
   105  			ClaimName          string `json:"claim.name"`
   106  			JSONTypeLabel      string `json:"jsonType.label"`
   107  		} `json:"config,omitempty"`
   108  	} `json:"protocolMappers"`
   109  	DefaultClientScopes  []string `json:"defaultClientScopes"`
   110  	OptionalClientScopes []string `json:"optionalClientScopes"`
   111  	Access               struct {
   112  		View      bool `json:"view"`
   113  		Configure bool `json:"configure"`
   114  		Manage    bool `json:"manage"`
   115  	} `json:"access"`
   116  }
   117  
   118  var volumeClaims map[string]*corev1.PersistentVolumeClaim
   119  
   120  var t = framework.NewTestFramework("keycloak")
   121  
   122  var isMinVersion140 bool
   123  var isMinVersion150 bool
   124  var isKeycloakEnabled bool
   125  var isArgoCDEnabled bool
   126  
   127  var beforeSuite = t.BeforeSuiteFunc(func() {
   128  	Eventually(func() (map[string]*corev1.PersistentVolumeClaim, error) {
   129  		var err error
   130  		volumeClaims, err = pkg.GetPersistentVolumeClaims(keycloakNamespace)
   131  		return volumeClaims, err
   132  	}, waitTimeout, pollingInterval).ShouldNot(BeNil())
   133  
   134  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   135  	if err != nil {
   136  		Fail(fmt.Sprintf("Failed to get default kubeconfig path: %s", err.Error()))
   137  	}
   138  	isKeycloakEnabled = pkg.IsKeycloakEnabled(kubeconfigPath)
   139  	isMinVersion140, err = pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfigPath)
   140  	isMinVersion150, err = pkg.IsVerrazzanoMinVersion("1.5.0", kubeconfigPath)
   141  	isArgoCDEnabled = pkg.IsArgoCDEnabled(kubeconfigPath)
   142  	if err != nil {
   143  		Fail(err.Error())
   144  	}
   145  })
   146  
   147  var _ = BeforeSuite(beforeSuite)
   148  
   149  var _ = t.AfterEach(func() {})
   150  
   151  var _ = t.Describe("Test Keycloak configuration.", Label("f:platform-lcm.install"), func() {
   152  	var _ = t.Context("Verify", func() {
   153  		isManagedClusterProfile := pkg.IsManagedClusterProfile()
   154  		t.It("master realm password policy", func() {
   155  			if !isManagedClusterProfile {
   156  				// GIVEN the password policy setup for the master realm during installation
   157  				// WHEN valid and invalid password changes are attempted
   158  				// THEN verify valid passwords are accepted and invalid passwords are rejected.
   159  				Eventually(verifyKeycloakMasterRealmPasswordPolicyIsCorrect, waitTimeout, pollingInterval).Should(BeTrue())
   160  			}
   161  		})
   162  		t.It("verrazzano-system realm password policy", func() {
   163  			if !isManagedClusterProfile {
   164  				// GIVEN the password policy setup for the verrazzano-system realm during installation
   165  				// WHEN valid and invalid password changes are attempted
   166  				// THEN verify valid passwords are accepted and invalid passwords are rejected.
   167  				Eventually(verifyKeycloakVerrazzanoRealmPasswordPolicyIsCorrect, waitTimeout, pollingInterval).Should(BeTrue())
   168  			}
   169  		})
   170  	})
   171  })
   172  
   173  var _ = t.Describe("Verify", Label("f:platform-lcm.install"), func() {
   174  	var _ = t.Context("MySQL Persistent Volumes in namespace keycloak based on", func() {
   175  		kubeconfigPath, _ := k8sutil.GetKubeConfigLocation()
   176  
   177  		size := "8Gi" // based on values set in platform-operator/thirdparty/charts/mysql
   178  		if ok, _ := pkg.IsVerrazzanoMinVersion("1.5.0", kubeconfigPath); ok {
   179  			size = "2Gi"
   180  		}
   181  		override, _ := pkg.GetEffectiveKeyCloakPersistenceOverride(kubeconfigPath)
   182  		if override != nil {
   183  			size = override.Spec.Resources.Requests.Storage().String()
   184  		}
   185  
   186  		claimName := "mysql"
   187  		if ok, _ := pkg.IsVerrazzanoMinVersion("1.5.0", kubeconfigPath); ok {
   188  			claimName = "datadir-mysql-0"
   189  		}
   190  
   191  		if pkg.IsDevProfile() {
   192  			expectedKeyCloakPVCs := 0
   193  			is15, _ := pkg.IsVerrazzanoMinVersion("1.5.0", kubeconfigPath)
   194  			if is15 {
   195  				expectedKeyCloakPVCs = 1
   196  			}
   197  			if override != nil {
   198  				expectedKeyCloakPVCs = 1
   199  			}
   200  			t.It("Dev install profile", func() {
   201  				// There is no Persistent Volume for MySQL in a dev install
   202  				Expect(len(volumeClaims)).To(Equal(expectedKeyCloakPVCs))
   203  				if expectedKeyCloakPVCs > 0 {
   204  					assertPersistentVolume(claimName, size)
   205  				}
   206  			})
   207  		} else if pkg.IsManagedClusterProfile() {
   208  			t.It("Managed Cluster install profile and verify namespace keycloak doesn't exist", func() {
   209  				// There is no keycloak namespace in a managed cluster install
   210  				Eventually(func() bool {
   211  					_, err := pkg.GetNamespace(keycloakNamespace)
   212  					return err != nil && errors.IsNotFound(err)
   213  				}, waitTimeout, pollingInterval).Should(BeTrue())
   214  			})
   215  		} else if pkg.IsProdProfile() {
   216  			t.It("Prod install profile", func() {
   217  				// Expect the number of claims to be equal to the number of MySQL replicas
   218  				mysqlStatefulSet, err := pkg.GetStatefulSet("keycloak", "mysql")
   219  				Expect(err).ShouldNot(HaveOccurred(), "Unexpected error obtaining MySQL statefulset")
   220  				expectedClaims := int(mysqlStatefulSet.Status.Replicas)
   221  				Expect(len(volumeClaims)).To(Equal(expectedClaims))
   222  				assertPersistentVolume(claimName, size)
   223  			})
   224  		}
   225  	})
   226  })
   227  
   228  var _ = t.Describe("Verify Keycloak", Label("f:platform-lcm.install"), func() {
   229  	var _ = t.Context("redirect and weborigins URIs", func() {
   230  		pkg.MinVersionSpec("Verify redirect and weborigins URIs", "1.1.0",
   231  			func() {
   232  				isManagedClusterProfile := pkg.IsManagedClusterProfile()
   233  				if !isManagedClusterProfile {
   234  					// GIVEN installation/upgrade of Keycloak has happened
   235  					// THEN verify that the correct redirect and weborigins URIs are created for verrazzano
   236  					Eventually(verifyKeycloakClientURIs, waitTimeout, pollingInterval).Should(BeTrue())
   237  				}
   238  			})
   239  	})
   240  })
   241  
   242  var _ = t.Describe("Verify client role", Label("f:platform-lcm.install"), func() {
   243  	t.It("Verify clients role for verrazzano user", func() {
   244  		isManagedClusterProfile := pkg.IsManagedClusterProfile()
   245  		// verrazzano user has the view-user role, starting from v1.4.0
   246  		if isKeycloakEnabled && isMinVersion140 && !isManagedClusterProfile {
   247  			Eventually(func() bool {
   248  				return verifyUserClientRole(vzUser, viewUsersRole)
   249  			}, waitTimeout, pollingInterval).Should(BeTrue())
   250  		} else {
   251  			t.Logs.Info("Skipping client role verification")
   252  		}
   253  	})
   254  })
   255  
   256  func verifyKeycloakVerrazzanoRealmPasswordPolicyIsCorrect() bool {
   257  	return verifyKeycloakRealmPasswordPolicyIsCorrect(constants.VerrazzanoOIDCSystemRealm)
   258  }
   259  
   260  func verifyKeycloakMasterRealmPasswordPolicyIsCorrect() bool {
   261  	return verifyKeycloakRealmPasswordPolicyIsCorrect("master")
   262  }
   263  
   264  func verifyKeycloakRealmPasswordPolicyIsCorrect(realm string) bool {
   265  	kc, err := pkg.NewKeycloakAdminRESTClient()
   266  	if err != nil {
   267  		t.Logs.Error(fmt.Printf("Failed to create Keycloak REST client: %v\n", err))
   268  		return false
   269  	}
   270  
   271  	var realmData map[string]interface{}
   272  	realmData, err = kc.GetRealm(realm)
   273  	if err != nil {
   274  		t.Logs.Error(fmt.Printf("Failed to get realm %s\n", realm))
   275  		return false
   276  	}
   277  	if realmData["passwordPolicy"] == nil {
   278  		t.Logs.Error(fmt.Printf("Failed to find password policy for realm: %s\n", realm))
   279  		return false
   280  	}
   281  	policy := realmData["passwordPolicy"].(string)
   282  	if len(policy) == 0 || !strings.Contains(policy, "length") {
   283  		t.Logs.Error(fmt.Printf("Failed to find password policy for realm: %s\n", realm))
   284  		return false
   285  	}
   286  
   287  	salt := time.Now().Format("20060102150405.000000000")
   288  	userName := fmt.Sprintf("test-user-%s", salt)
   289  	firstName := fmt.Sprintf("test-first-%s", salt)
   290  	lastName := fmt.Sprintf("test-last-%s", salt)
   291  	validPassword := fmt.Sprintf("test-password-12-!@-AB-%s", salt)
   292  	userURL, err := kc.CreateUser(realm, userName, firstName, lastName, validPassword)
   293  	if err != nil {
   294  		t.Logs.Error(fmt.Printf("Failed to create user %s/%s: %v\n", realm, userName, err))
   295  		return false
   296  	}
   297  	userID := path.Base(userURL)
   298  	defer func() {
   299  		err = kc.DeleteUser(realm, userID)
   300  		if err != nil {
   301  			t.Logs.Info(fmt.Printf("Failed to delete user %s/%s: %v\n", realm, userID, err))
   302  		}
   303  	}()
   304  	err = kc.SetPassword(realm, userID, "invalid")
   305  	if err == nil {
   306  		t.Logs.Error(fmt.Printf("Should not have been able to set password for %s/%s\n", realm, userID))
   307  		return false
   308  	}
   309  	newValidPassword := fmt.Sprintf("test-new-password-12-!@-AB-%s", salt)
   310  	err = kc.SetPassword(realm, userID, newValidPassword)
   311  	if err != nil {
   312  		t.Logs.Error(fmt.Printf("Failed to set password for %s/%s: %v\n", realm, userID, err))
   313  		return false
   314  	}
   315  	return true
   316  }
   317  
   318  func verifyKeycloakClientURIs() bool {
   319  	var keycloakClients KeycloakClients
   320  
   321  	// Login to Keycloak
   322  	if !loginKeycloak() {
   323  		return false
   324  	}
   325  
   326  	// Get the Client ID JSON array
   327  	cmd := exec.Command("kubectl", "exec", "keycloak-0", "-n", "keycloak", "-c", "keycloak", "--", kcAdminScript, "get", "clients", "-r", constants.VerrazzanoOIDCSystemRealm, "--fields", "id,clientId") //nolint:gosec //#nosec G204
   328  	out, err := cmd.Output()
   329  	if err != nil {
   330  		t.Logs.Error(fmt.Printf("Error retrieving ID for client ID, zero length: %s\n", err))
   331  		return false
   332  	}
   333  
   334  	if len(string(out)) == 0 {
   335  		t.Logs.Error(fmt.Print("Error retrieving Clients JSON from Keycloak, zero length, zero length\n"))
   336  		return false
   337  	}
   338  
   339  	err = json.Unmarshal([]byte(out), &keycloakClients)
   340  	if err != nil {
   341  		t.Logs.Error(fmt.Sprintf("error unmarshalling keycloak client json %v", err.Error()))
   342  		return false
   343  	}
   344  
   345  	// Verify Num URIs per product endpoint
   346  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   347  	if err != nil {
   348  		t.Logs.Error(fmt.Printf("Error retrieving Kubeconfig Path: %s\n", err))
   349  		return false
   350  	}
   351  	env, err := pkg.GetEnvName(kubeconfigPath)
   352  	if err != nil {
   353  		t.Logs.Error(fmt.Printf("Error retrieving Verrazzano Env: %s\n", err))
   354  		return false
   355  	}
   356  
   357  	keycloakClient, err := getKeycloakClientByClientID(keycloakClients, "verrazzano-pkce")
   358  	if err != nil {
   359  		t.Logs.Error(fmt.Printf("Error retrieving Verrazzano pkce client: %s\n", err))
   360  		return false
   361  	}
   362  
   363  	if !verifyVerrazzanoPKCEClientURIs(keycloakClient, env) {
   364  		return false
   365  	}
   366  
   367  	if isMinVersion140 {
   368  		keycloakClient, err = getKeycloakClientByClientID(keycloakClients, "rancher")
   369  		if err != nil {
   370  			t.Logs.Error(fmt.Printf("Error retrieving Verrazzano rancher client: %s\n", err))
   371  			return false
   372  		}
   373  
   374  		if !verifyRancherClientURIs(keycloakClient, env) {
   375  			return false
   376  		}
   377  	}
   378  
   379  	if isArgoCDEnabled {
   380  		keycloakClient, err = getKeycloakClientByClientID(keycloakClients, "argocd")
   381  		if err != nil {
   382  			t.Logs.Error(fmt.Printf("Error retrieving Verrazzano argocd client: %s\n", err))
   383  			return false
   384  		}
   385  
   386  		if !verifyArgoCDClientURIs(keycloakClient, env) {
   387  			return false
   388  		}
   389  	}
   390  
   391  	return true
   392  }
   393  
   394  func assertPersistentVolume(key string, size string) {
   395  	Expect(volumeClaims).To(HaveKey(key))
   396  	pvc := volumeClaims[key]
   397  	Expect(pvc.Spec.Resources.Requests.Storage().String()).To(Equal(size))
   398  }
   399  
   400  func verifyURIs(uriArray []string, name string, numToFind int) bool {
   401  	ctr := 0
   402  	for _, uri := range uriArray {
   403  		if strings.Contains(uri, name) {
   404  			ctr++
   405  		}
   406  	}
   407  	return ctr == numToFind
   408  }
   409  
   410  func getKeycloakClientByClientID(keycloakClients KeycloakClients, clientID string) (*Client, error) {
   411  	// Extract the id associated with ClientID
   412  	var keycloakClient Client
   413  	var id = ""
   414  	for _, client := range keycloakClients {
   415  		if client.ClientID == clientID {
   416  			id = client.ID
   417  			t.Logs.Info(fmt.Printf("Keycloak Clients ID found = %s\n", id))
   418  		}
   419  	}
   420  	if id == "" {
   421  		err := fmt.Errorf("error retrieving ID for Keycloak user, zero length")
   422  		t.Logs.Error(err.Error())
   423  		return nil, err
   424  	}
   425  
   426  	// Get the client Info
   427  	client := "clients/" + id
   428  	cmd := exec.Command("kubectl", "exec", "keycloak-0", "-n", "keycloak", "-c", "keycloak", "--", kcAdminScript, "get", client, "-r", constants.VerrazzanoOIDCSystemRealm) //nolint:gosec //#nosec G204
   429  	out, err := cmd.Output()
   430  	if err != nil {
   431  		err := fmt.Errorf("error retrieving clientID json: %s", err)
   432  		t.Logs.Error(err.Error())
   433  		return nil, err
   434  	}
   435  
   436  	if len(string(out)) == 0 {
   437  		err := fmt.Errorf("error retrieving client json from keycloak, zero length")
   438  		t.Logs.Error(err.Error())
   439  		return nil, err
   440  	}
   441  
   442  	err = json.Unmarshal([]byte(out), &keycloakClient)
   443  	if err != nil {
   444  		err := fmt.Errorf("error unmarshalling keycloak client %s", err.Error())
   445  		t.Logs.Error(err.Error())
   446  		return nil, err
   447  	}
   448  
   449  	return &keycloakClient, nil
   450  }
   451  
   452  func verifyVerrazzanoPKCEClientURIs(keycloakClient *Client, env string) bool {
   453  	// Verify Correct number of RedirectURIs
   454  	// 25 redirect Uris for new installation
   455  	// 29 redirect Uris for upgrade from older versions. The urls are deprecated ingress hosts.
   456  	if isMinVersion150 {
   457  		if !(len(keycloakClient.RedirectUris) == 29 || len(keycloakClient.RedirectUris) == 25) {
   458  			t.Logs.Error(fmt.Printf("Incorrect Number of Redirect URIs returned for client %+v\n", keycloakClient.RedirectUris))
   459  			return false
   460  		}
   461  	} else if !isMinVersion150 && len(keycloakClient.RedirectUris) != 29 {
   462  		t.Logs.Error(fmt.Printf("Incorrect Number of Redirect URIs returned for client %+v\n", keycloakClient.RedirectUris))
   463  		return false
   464  	}
   465  
   466  	// Verify Correct number of WebOrigins
   467  	if isMinVersion150 {
   468  		if !(len(keycloakClient.WebOrigins) == 15 || len(keycloakClient.WebOrigins) == 13) {
   469  			t.Logs.Error(fmt.Printf("Incorrect Number of WebOrigins returned for client %+v\n", keycloakClient.WebOrigins))
   470  			return false
   471  		}
   472  	} else if !isMinVersion150 && len(keycloakClient.WebOrigins) != 13 {
   473  		t.Logs.Error(fmt.Printf("Incorrect Number of WebOrigins returned for client %+v\n", keycloakClient.WebOrigins))
   474  		return false
   475  	}
   476  
   477  	// Kiali
   478  	if !verifyURIs(keycloakClient.RedirectUris, kialiURI+env, 2) {
   479  		t.Logs.Error(fmt.Printf("Expected 2 Kiali redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   480  		return false
   481  	}
   482  
   483  	if !verifyURIs(keycloakClient.WebOrigins, kialiURI+env, 1) {
   484  		t.Logs.Error(fmt.Printf("Expected 1 Kiali weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   485  		return false
   486  	}
   487  
   488  	// Prometheus
   489  	if !verifyURIs(keycloakClient.RedirectUris, prometheusURI+env, 2) {
   490  		t.Logs.Error(fmt.Printf("Expected 2 Prometheus redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   491  		return false
   492  	}
   493  
   494  	if !verifyURIs(keycloakClient.WebOrigins, prometheusURI+env, 1) {
   495  		t.Logs.Error(fmt.Printf("Expected 1 Prometheus weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   496  		return false
   497  	}
   498  
   499  	// Grafana
   500  	if !verifyURIs(keycloakClient.RedirectUris, grafanaURI+env, 2) {
   501  		t.Logs.Error(fmt.Printf("Expected 2 Grafana redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   502  		return false
   503  	}
   504  
   505  	if !verifyURIs(keycloakClient.WebOrigins, grafanaURI+env, 1) {
   506  		t.Logs.Error(fmt.Printf("Expected 1 Grafana weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   507  		return false
   508  	}
   509  
   510  	// Opensearch
   511  	if !verifyURIs(keycloakClient.RedirectUris, osURI+env, 2) {
   512  		t.Logs.Error(fmt.Printf("Expected 2 Opensearch redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   513  		return false
   514  	}
   515  
   516  	if !verifyURIs(keycloakClient.WebOrigins, osURI+env, 1) {
   517  		t.Logs.Error(fmt.Printf("Expected 1 Opensearch weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   518  		return false
   519  	}
   520  
   521  	// Opensearchdashboards
   522  	if !(isMinVersion150 && verifyURIs(keycloakClient.RedirectUris, osdURI+env, 2)) {
   523  		t.Logs.Error(fmt.Printf("Expected 2 Opensearchdashboards redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   524  		return false
   525  	}
   526  
   527  	if !verifyURIs(keycloakClient.WebOrigins, osdURI+env, 1) {
   528  		t.Logs.Error(fmt.Printf("Expected 1 Opensearchdashboards weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   529  		return false
   530  	}
   531  
   532  	// Verrazzano
   533  	if !verifyURIs(keycloakClient.RedirectUris, verrazzanoURI+env, 2) {
   534  		t.Logs.Error(fmt.Printf("Expected 2 Verrazzano redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   535  		return false
   536  	}
   537  
   538  	if !verifyURIs(keycloakClient.WebOrigins, verrazzanoURI+env, 1) {
   539  		t.Logs.Error(fmt.Printf("Expected 1 Verrazzano weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   540  		return false
   541  	}
   542  
   543  	// Alertmanager
   544  	if !verifyURIs(keycloakClient.RedirectUris, alertmanagerURI+env, 2) {
   545  		t.Logs.Error(fmt.Printf("Expected 2 Alertmanager redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   546  		return false
   547  	}
   548  
   549  	if !verifyURIs(keycloakClient.WebOrigins, alertmanagerURI+env, 1) {
   550  		t.Logs.Error(fmt.Printf("Expected 1 Alertmanager weborigin URIs. Found %+v\n", keycloakClient.WebOrigins))
   551  		return false
   552  	}
   553  
   554  	return true
   555  }
   556  
   557  func verifyRancherClientURIs(keycloakClient *Client, env string) bool {
   558  	// Verify Correct number of RedirectURIs
   559  	if len(keycloakClient.RedirectUris) != 1 {
   560  		t.Logs.Error(fmt.Printf("Incorrect Number of Redirect URIs returned for client %+v\n", keycloakClient.RedirectUris))
   561  		return false
   562  	}
   563  
   564  	// Verify Correct number of WebOrigins
   565  	if len(keycloakClient.WebOrigins) != 1 {
   566  		t.Logs.Error(fmt.Printf("Incorrect Number of WebOrigins returned for client %+v\n", keycloakClient.WebOrigins))
   567  		return false
   568  	}
   569  
   570  	// Verify rancher redirectUI
   571  	if !verifyURIs(keycloakClient.RedirectUris, rancherURI+env, 1) {
   572  		t.Logs.Error(fmt.Printf("Expected 1 Rancher redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   573  		return false
   574  	}
   575  	// Verify rancher web origin
   576  	if !verifyURIs(keycloakClient.WebOrigins, rancherURI+env, 1) {
   577  		t.Logs.Error(fmt.Printf("Expected 1 Rancher weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   578  		return false
   579  	}
   580  
   581  	return true
   582  }
   583  
   584  func verifyArgoCDClientURIs(keycloakClient *Client, env string) bool {
   585  	// Verify Correct number of RedirectURIs
   586  	if len(keycloakClient.RedirectUris) != 1 {
   587  		t.Logs.Error(fmt.Printf("Incorrect Number of Redirect URIs returned for client %+v\n", keycloakClient.RedirectUris))
   588  		return false
   589  	}
   590  
   591  	// Verify Correct number of WebOrigins
   592  	if len(keycloakClient.WebOrigins) != 1 {
   593  		t.Logs.Error(fmt.Printf("Incorrect Number of WebOrigins returned for client %+v\n", keycloakClient.WebOrigins))
   594  		return false
   595  	}
   596  
   597  	// Verify Argo CD redirectUI
   598  	if !verifyURIs(keycloakClient.RedirectUris, argocdURI+env, 1) {
   599  		t.Logs.Error(fmt.Printf("Expected 1 ArgoCD redirect URIs. Found %+v\n", keycloakClient.RedirectUris))
   600  		return false
   601  	}
   602  	// Verify Argo CD web origin
   603  	if !verifyURIs(keycloakClient.WebOrigins, argocdURI+env, 1) {
   604  		t.Logs.Error(fmt.Printf("Expected 1 ArgoCD weborigin URIs. Found %+v\n", keycloakClient.RedirectUris))
   605  		return false
   606  	}
   607  
   608  	return true
   609  }
   610  
   611  // loginKeycloak logs into master realm, by calling kcadmin.sh config credentials
   612  func loginKeycloak() bool {
   613  	// Get the Keycloak admin password
   614  	secret, err := pkg.GetSecret("keycloak", "keycloak-http")
   615  	if err != nil {
   616  		t.Logs.Error(fmt.Printf("Failed to get KeyCloak secret: %s\n", err))
   617  		return false
   618  	}
   619  	pw := secret.Data["password"]
   620  	keycloakpw := string(pw)
   621  	if keycloakpw == "" {
   622  		t.Logs.Error(fmt.Print("Invalid Keycloak password. Empty String returned"))
   623  		return false
   624  	}
   625  
   626  	// Login to Keycloak
   627  	cmd := exec.Command("kubectl", "exec", "keycloak-0", "-n", "keycloak", "-c", "keycloak", "--",
   628  		kcAdminScript, "config", "credentials", "--server", "http://localhost:8080/auth", "--realm", "master", "--user", "keycloakadmin", "--password", keycloakpw)
   629  	_, err = cmd.Output()
   630  	if err != nil {
   631  		t.Logs.Error(fmt.Printf("Error logging into Keycloak: %s\n", err))
   632  		return false
   633  	}
   634  	return true
   635  }
   636  
   637  // verifyUserClientRole verifies whether user has the specified client role
   638  func verifyUserClientRole(user, userRole string) bool {
   639  	var kcRoles keycloakRoles
   640  
   641  	// Login to Keycloak
   642  	if !loginKeycloak() {
   643  		return false
   644  	}
   645  
   646  	// Get the roles for the user
   647  	cmd := exec.Command("kubectl", "exec", "keycloak-0", "-n", "keycloak", "-c", "keycloak", "--", kcAdminScript, "get-roles", "-r", constants.VerrazzanoOIDCSystemRealm, "--uusername", user, "--cclientid", realmMgmt, "--effective", "--fields", "name") //nolint:gosec //#nosec G204
   648  	out, err := cmd.Output()
   649  	if err != nil {
   650  		t.Logs.Error(fmt.Printf("Error retrieving client role for the user %s: %s\n", vzUser, err.Error()))
   651  		return false
   652  	}
   653  
   654  	if len(string(out)) == 0 {
   655  		t.Logs.Error(fmt.Print("Client roles retrieved from Keycloak is of zero length\n"))
   656  		return false
   657  	}
   658  
   659  	err = json.Unmarshal([]byte(out), &kcRoles)
   660  	if err != nil {
   661  		t.Logs.Error(fmt.Sprintf("Error unmarshalling Keycloak client role, received as JSON: %s\n", err.Error()))
   662  		return false
   663  	}
   664  
   665  	for _, role := range kcRoles {
   666  		if role.Name == userRole {
   667  			t.Logs.Info(fmt.Printf("Client role %s found\n", userRole))
   668  			return true
   669  		}
   670  	}
   671  	return false
   672  }