You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by th...@apache.org on 2021/10/06 16:25:34 UTC

[solr-operator] branch main updated: Refactor security related code into a separate solr_security_util.go vs. sprinkled throughout the codebase. (#334)

This is an automated email from the ASF dual-hosted git repository.

thelabdude pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 75bdb0d  Refactor security related code into a separate solr_security_util.go vs. sprinkled throughout the codebase. (#334)
75bdb0d is described below

commit 75bdb0dc05e80fd362e7c2e04fb18faf30f395f6
Author: Timothy Potter <th...@gmail.com>
AuthorDate: Wed Oct 6 10:25:28 2021 -0600

    Refactor security related code into a separate solr_security_util.go vs. sprinkled throughout the codebase. (#334)
---
 controllers/solrcloud_controller.go          |  91 +-----
 controllers/solrcloud_controller_tls_test.go |   3 +
 controllers/util/prometheus_exporter_util.go |   6 +-
 controllers/util/solr_security_util.go       | 433 +++++++++++++++++++++++++++
 controllers/util/solr_tls_util.go            |   3 +-
 controllers/util/solr_util.go                | 293 ++----------------
 6 files changed, 468 insertions(+), 361 deletions(-)

diff --git a/controllers/solrcloud_controller.go b/controllers/solrcloud_controller.go
index cc27f62..19df029 100644
--- a/controllers/solrcloud_controller.go
+++ b/controllers/solrcloud_controller.go
@@ -279,86 +279,13 @@ func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 		}
 	}
 
-	basicAuthHeader := ""
+	// Holds security config info needed during construction of the StatefulSet
+	var security *util.SecurityConfig = nil
 	if instance.Spec.SolrSecurity != nil {
-		sec := instance.Spec.SolrSecurity
-
-		if sec.AuthenticationType != solrv1beta1.Basic {
-			return requeueOrNot, fmt.Errorf("%s not supported! Only 'Basic' authentication is supported by the Solr operator",
-				instance.Spec.SolrSecurity.AuthenticationType)
-		}
-
-		// for now, we don't support 'solrSecurity.probesRequireAuth=true' and custom probe paths,
-		// so make the user fix that so there are no surprises later
-		if sec.ProbesRequireAuth && instance.Spec.CustomSolrKubeOptions.PodOptions != nil {
-			for _, path := range util.GetCustomProbePaths(instance) {
-				if path != util.DefaultProbePath {
-					return requeueOrNot, fmt.Errorf(
-						"custom probe path %s not supported when 'solrSecurity.probesRequireAuth=true'; must use 'solrSecurity.probesRequireAuth=false' when using custom probe endpoints", path)
-				}
-			}
-		}
-
-		basicAuthSecret := &corev1.Secret{}
-
-		// user has the option of providing a secret with credentials the operator should use to make requests to Solr
-		if sec.BasicAuthSecret != "" {
-			if err := r.Get(ctx, types.NamespacedName{Name: sec.BasicAuthSecret, Namespace: instance.Namespace}, basicAuthSecret); err != nil {
-				return requeueOrNot, err
-			}
-
-			err = util.ValidateBasicAuthSecret(basicAuthSecret)
-			if err != nil {
-				return requeueOrNot, err
-			}
-
-		} else {
-			// We're supplying a secret with random passwords and a default security.json
-			// since we randomly generate the passwords, we need to lookup the secret first and only create if not exist
-			err = r.Get(ctx, types.NamespacedName{Name: instance.BasicAuthSecretName(), Namespace: instance.Namespace}, basicAuthSecret)
-			if err != nil && errors.IsNotFound(err) {
-				authSecret, bootstrapSecret := util.GenerateBasicAuthSecretWithBootstrap(instance)
-				if err := controllerutil.SetControllerReference(instance, authSecret, r.Scheme); err != nil {
-					return requeueOrNot, err
-				}
-				if err := controllerutil.SetControllerReference(instance, bootstrapSecret, r.Scheme); err != nil {
-					return requeueOrNot, err
-				}
-				err = r.Create(ctx, authSecret)
-				if err != nil {
-					return requeueOrNot, err
-				}
-				err = r.Create(ctx, bootstrapSecret)
-				if err == nil {
-					// supply the bootstrap security.json to the initContainer via a simple BASE64 encoding env var
-					reconcileConfigInfo[util.SecurityJsonFile] = string(bootstrapSecret.Data[util.SecurityJsonFile])
-				}
-
-				basicAuthSecret = authSecret
-			}
-			if err != nil {
-				return requeueOrNot, err
-			}
-
-			if reconcileConfigInfo[util.SecurityJsonFile] == "" {
-				// the bootstrap secret already exists, so just stash the security.json needed for constructing initContainers
-				bootstrapSecret := &corev1.Secret{}
-				err = r.Get(ctx, types.NamespacedName{Name: instance.SecurityBootstrapSecretName(), Namespace: instance.Namespace}, bootstrapSecret)
-				if err != nil {
-					if !errors.IsNotFound(err) {
-						return requeueOrNot, err
-					} // else perhaps the user deleted it after security was bootstrapped ... this is ok but may trigger a restart on the STS
-				} else {
-					// stash this so we can configure the setup-zk initContainer to bootstrap the security.json in ZK
-					reconcileConfigInfo[util.SecurityJsonFile] = string(bootstrapSecret.Data[util.SecurityJsonFile])
-				}
-			}
+		security, err = util.ReconcileSecurityConfig(ctx, &r.Client, instance)
+		if err != nil {
+			return requeueOrNot, err
 		}
-
-		reconcileConfigInfo[corev1.BasicAuthUsernameKey] = string(basicAuthSecret.Data[corev1.BasicAuthUsernameKey])
-
-		// need the creds below for getting CLUSTERSTATUS
-		basicAuthHeader = util.BasicAuthHeader(basicAuthSecret)
 	}
 
 	// Only create stateful set if zkConnectionString can be found (must contain host and port)
@@ -387,7 +314,7 @@ func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 
 	if !blockReconciliationOfStatefulSet {
 		// Generate StatefulSet
-		statefulSet := util.GenerateStatefulSet(instance, &newStatus, hostNameIpMap, reconcileConfigInfo, tls)
+		statefulSet := util.GenerateStatefulSet(instance, &newStatus, hostNameIpMap, reconcileConfigInfo, tls, security)
 
 		// Check if the StatefulSet already exists
 		statefulSetLogger := logger.WithValues("statefulSet", statefulSet.Name)
@@ -481,10 +408,10 @@ func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 			logger.Info("Pod killed for update.", "pod", pod.Name, "reason", "The solr container in the pod has not yet started, thus it is safe to update.")
 		}
 
-		// If authn enabled on Solr, we need to pass the basic auth header
+		// If authn enabled on Solr, we need to pass the auth header
 		var authHeader map[string]string
-		if basicAuthHeader != "" {
-			authHeader = map[string]string{"Authorization": basicAuthHeader}
+		if security != nil {
+			authHeader = security.AuthHeader()
 		}
 
 		// Pick which pods should be deleted for an update.
diff --git a/controllers/solrcloud_controller_tls_test.go b/controllers/solrcloud_controller_tls_test.go
index 5c2b2bd..b5d1b2e 100644
--- a/controllers/solrcloud_controller_tls_test.go
+++ b/controllers/solrcloud_controller_tls_test.go
@@ -54,6 +54,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() {
 				Replicas: &replicas,
 				ZookeeperRef: &solrv1beta1.ZookeeperRef{
 					ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+						ChRoot:                   "tls-test",
 						InternalConnectionString: "host:7271",
 					},
 				},
@@ -795,6 +796,7 @@ func expectZkSetupInitContainerForTLSWithGomega(g Gomega, solrCloud *solrv1beta1
 			break
 		}
 	}
+	expChrootCmd := "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER};"
 	expCmd := "/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd clusterprop -name urlScheme -val https"
 	if solrCloud.Spec.SolrTLS != nil {
 		g.Expect(zkSetupInitContainer).To(Not(BeNil()), "Didn't find the zk-setup InitContainer in the sts!")
@@ -802,6 +804,7 @@ func expectZkSetupInitContainerForTLSWithGomega(g Gomega, solrCloud *solrv1beta1
 			g.Expect(zkSetupInitContainer.Image).To(Equal(statefulSet.Spec.Template.Spec.Containers[0].Image), "The zk-setup init container should use the same image as the Solr container")
 			g.Expect(zkSetupInitContainer.Command).To(HaveLen(3), "Wrong command length for zk-setup init container")
 			g.Expect(zkSetupInitContainer.Command[2]).To(ContainSubstring(expCmd), "ZK Setup command does not set urlScheme")
+			g.Expect(zkSetupInitContainer.Command[2]).To(ContainSubstring(expChrootCmd), "ZK Setup command does init the chroot")
 			expNumVars := 3
 			if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.BasicAuthSecret == "" {
 				expNumVars = 4 // one more for SECURITY_JSON
diff --git a/controllers/util/prometheus_exporter_util.go b/controllers/util/prometheus_exporter_util.go
index f5a122a..fb92a68 100644
--- a/controllers/util/prometheus_exporter_util.go
+++ b/controllers/util/prometheus_exporter_util.go
@@ -164,11 +164,7 @@ func GenerateSolrPrometheusExporterDeployment(solrPrometheusExporter *solr.SolrP
 
 	// basic auth enabled?
 	if solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret != "" {
-		lor := corev1.LocalObjectReference{Name: solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret}
-		usernameRef := &corev1.SecretKeySelector{LocalObjectReference: lor, Key: corev1.BasicAuthUsernameKey}
-		passwordRef := &corev1.SecretKeySelector{LocalObjectReference: lor, Key: corev1.BasicAuthPasswordKey}
-		envVars = append(envVars, corev1.EnvVar{Name: "BASIC_AUTH_USER", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: usernameRef}})
-		envVars = append(envVars, corev1.EnvVar{Name: "BASIC_AUTH_PASS", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: passwordRef}})
+		envVars = append(envVars, BasicAuthEnvVars(solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret)...)
 		allJavaOpts = append(allJavaOpts, "-Dbasicauth=$(BASIC_AUTH_USER):$(BASIC_AUTH_PASS)")
 		allJavaOpts = append(allJavaOpts, "-Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory")
 	}
diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go
new file mode 100644
index 0000000..9701ab2
--- /dev/null
+++ b/controllers/util/solr_security_util.go
@@ -0,0 +1,433 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import (
+	"context"
+	"crypto/sha256"
+	b64 "encoding/base64"
+	"encoding/json"
+	"fmt"
+	solr "github.com/apache/solr-operator/api/v1beta1"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"math/rand"
+	"regexp"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+	"strings"
+	"time"
+)
+
+const (
+	SecurityJsonFile       = "security.json"
+	BasicAuthMd5Annotation = "solr.apache.org/basicAuthMd5"
+	DefaultProbePath       = "/admin/info/system"
+)
+
+type SecurityConfig struct {
+	BasicAuthSecret *corev1.Secret
+	SecurityJson    string
+}
+
+// Given a SolrCloud instance and an API service client, produce a SecurityConfig needed to enable Solr security
+func ReconcileSecurityConfig(ctx context.Context, client *client.Client, instance *solr.SolrCloud) (*SecurityConfig, error) {
+	reader := *client
+
+	security := &SecurityConfig{}
+	basicAuthSecret := &corev1.Secret{}
+
+	// user has the option of providing a secret with credentials the operator should use to make requests to Solr
+	sec := instance.Spec.SolrSecurity
+
+	if sec.AuthenticationType != solr.Basic {
+		return nil, fmt.Errorf("%s not supported! Only 'Basic' authentication is supported by the Solr operator",
+			instance.Spec.SolrSecurity.AuthenticationType)
+	}
+
+	// TODO: we shouldn't need to enforce this restriction?!?
+	//
+	// for now, we don't support 'solrSecurity.probesRequireAuth=true' and custom probe paths,
+	// so make the user fix that so there are no surprises later
+	if sec.ProbesRequireAuth && instance.Spec.CustomSolrKubeOptions.PodOptions != nil {
+		for _, path := range GetCustomProbePaths(instance) {
+			if path != DefaultProbePath {
+				return nil, fmt.Errorf(
+					"custom probe path %s not supported when 'solrSecurity.probesRequireAuth=true'; must use 'solrSecurity.probesRequireAuth=false' when using custom probe endpoints", path)
+			}
+		}
+	}
+
+	if sec.BasicAuthSecret != "" {
+		// the user supplied their own basic auth secret, make sure it exists and has the expected keys
+		if err := reader.Get(ctx, types.NamespacedName{Name: sec.BasicAuthSecret, Namespace: instance.Namespace}, basicAuthSecret); err != nil {
+			return nil, err
+		}
+
+		err := ValidateBasicAuthSecret(basicAuthSecret)
+		if err != nil {
+			return nil, err
+		}
+
+		// since the user supplied us with a basic auth secret, we're assuming they're also bootstrapping the security.json,
+		// so there is no bootstrap secret in this case
+
+	} else {
+		// We're supplying a secret with random passwords and a default security.json
+		// since we randomly generate the passwords, we need to lookup the secret first and only create if not exist
+		err := reader.Get(ctx, types.NamespacedName{Name: instance.BasicAuthSecretName(), Namespace: instance.Namespace}, basicAuthSecret)
+		if err != nil && errors.IsNotFound(err) {
+			authSecret, bootstrapSecret := generateBasicAuthSecretWithBootstrap(instance)
+
+			// take ownership of these secrets since we created them
+			if err := controllerutil.SetControllerReference(instance, authSecret, reader.Scheme()); err != nil {
+				return nil, err
+			}
+			if err := controllerutil.SetControllerReference(instance, bootstrapSecret, reader.Scheme()); err != nil {
+				return nil, err
+			}
+			err = reader.Create(ctx, authSecret)
+			if err != nil {
+				return nil, err
+			}
+			err = reader.Create(ctx, bootstrapSecret)
+			if err != nil {
+				return nil, err
+			}
+
+			// supply the bootstrap security.json to the initContainer via a simple BASE64 encoding env var
+			security.SecurityJson = string(bootstrapSecret.Data[SecurityJsonFile])
+			basicAuthSecret = authSecret
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		security.BasicAuthSecret = basicAuthSecret
+
+		if security.SecurityJson == "" {
+			// the bootstrap secret already exists, so just stash the security.json needed for constructing initContainers
+			bootstrapSecret := &corev1.Secret{}
+			err = reader.Get(ctx, types.NamespacedName{Name: instance.SecurityBootstrapSecretName(), Namespace: instance.Namespace}, bootstrapSecret)
+			if err != nil {
+				if !errors.IsNotFound(err) {
+					return nil, err
+				} // else perhaps the user deleted it after security was bootstrapped ... this is ok but may trigger a restart on the STS
+			} else {
+				// stash this so we can configure the setup-zk initContainer to bootstrap the security.json in ZK
+				security.SecurityJson = string(bootstrapSecret.Data[SecurityJsonFile])
+			}
+		}
+	}
+
+	return security, nil
+}
+
+func enableSecureProbesOnSolrCloudStatefulSet(solrCloud *solr.SolrCloud, stateful *appsv1.StatefulSet) {
+	mainContainer := &stateful.Spec.Template.Spec.Containers[0]
+
+	// if probes require auth or Solr wants client auth (mTLS), need to invoke a command on the Solr pod for the probes
+	mountPath := ""
+	if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.ProbesRequireAuth {
+		vol, volMount := secureProbeVolumeAndMount(solrCloud.BasicAuthSecretName())
+		if vol != nil {
+			stateful.Spec.Template.Spec.Volumes = append(stateful.Spec.Template.Spec.Volumes, *vol)
+		}
+		if volMount != nil {
+			mainContainer.VolumeMounts = append(mainContainer.VolumeMounts, *volMount)
+			mountPath = volMount.MountPath
+		}
+	}
+
+	// update the probes if they are using HTTPGet to use an Exec to call Solr with TLS and/or Basic Auth creds
+	if mainContainer.LivenessProbe.HTTPGet != nil {
+		useSecureProbe(solrCloud, mainContainer.LivenessProbe, mountPath)
+	}
+	if mainContainer.ReadinessProbe.HTTPGet != nil {
+		useSecureProbe(solrCloud, mainContainer.ReadinessProbe, mountPath)
+	}
+	if mainContainer.StartupProbe != nil && mainContainer.StartupProbe.HTTPGet != nil {
+		useSecureProbe(solrCloud, mainContainer.StartupProbe, mountPath)
+	}
+}
+
+func cmdToPutSecurityJsonInZk() string {
+	scriptsDir := "/opt/solr/server/scripts/cloud-scripts"
+	cmd := " ZK_SECURITY_JSON=$(%s/zkcli.sh -zkhost ${ZK_HOST} -cmd get /security.json); "
+	cmd += "if [ ${#ZK_SECURITY_JSON} -lt 3 ]; then echo $SECURITY_JSON > /tmp/security.json; %s/zkcli.sh -zkhost ${ZK_HOST} -cmd putfile /security.json /tmp/security.json; echo \"put security.json in ZK\"; fi"
+	return fmt.Sprintf(cmd, scriptsDir, scriptsDir)
+}
+
+func (security *SecurityConfig) AuthHeader() map[string]string {
+	if security.BasicAuthSecret != nil {
+		return map[string]string{"Authorization": BasicAuthHeader(security.BasicAuthSecret)}
+	}
+	return nil
+}
+
+func BasicAuthEnvVars(secretName string) []corev1.EnvVar {
+	lor := corev1.LocalObjectReference{Name: secretName}
+	usernameRef := &corev1.SecretKeySelector{LocalObjectReference: lor, Key: corev1.BasicAuthUsernameKey}
+	passwordRef := &corev1.SecretKeySelector{LocalObjectReference: lor, Key: corev1.BasicAuthPasswordKey}
+	return []corev1.EnvVar{
+		{Name: "BASIC_AUTH_USER", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: usernameRef}},
+		{Name: "BASIC_AUTH_PASS", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: passwordRef}},
+	}
+}
+
+func BasicAuthHeader(basicAuthSecret *corev1.Secret) string {
+	creds := fmt.Sprintf("%s:%s", basicAuthSecret.Data[corev1.BasicAuthUsernameKey], basicAuthSecret.Data[corev1.BasicAuthPasswordKey])
+	return "Basic " + b64.StdEncoding.EncodeToString([]byte(creds))
+}
+
+func ValidateBasicAuthSecret(basicAuthSecret *corev1.Secret) error {
+	if basicAuthSecret.Type != corev1.SecretTypeBasicAuth {
+		return fmt.Errorf("invalid secret type %v; user-provided secret %s must be of type: %v",
+			basicAuthSecret.Type, basicAuthSecret.Name, corev1.SecretTypeBasicAuth)
+	}
+
+	if _, ok := basicAuthSecret.Data[corev1.BasicAuthUsernameKey]; !ok {
+		return fmt.Errorf("%s key not found in user-provided basic-auth secret %s",
+			corev1.BasicAuthUsernameKey, basicAuthSecret.Name)
+	}
+
+	if _, ok := basicAuthSecret.Data[corev1.BasicAuthPasswordKey]; !ok {
+		return fmt.Errorf("%s key not found in user-provided basic-auth secret %s",
+			corev1.BasicAuthPasswordKey, basicAuthSecret.Name)
+	}
+
+	return nil
+}
+
+func generateBasicAuthSecretWithBootstrap(solrCloud *solr.SolrCloud) (*corev1.Secret, *corev1.Secret) {
+	securityBootstrapInfo := generateSecurityJson(solrCloud)
+
+	labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
+	var annotations map[string]string
+	basicAuthSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:        solrCloud.BasicAuthSecretName(),
+			Namespace:   solrCloud.GetNamespace(),
+			Labels:      labels,
+			Annotations: annotations,
+		},
+		Data: map[string][]byte{
+			corev1.BasicAuthUsernameKey: []byte(solr.DefaultBasicAuthUsername),
+			corev1.BasicAuthPasswordKey: securityBootstrapInfo[solr.DefaultBasicAuthUsername],
+		},
+		Type: corev1.SecretTypeBasicAuth,
+	}
+
+	// this secret holds the admin and solr user credentials and the security.json needed to bootstrap Solr security
+	// once the security.json is created using the setup-zk initContainer, it is not updated by the operator
+	boostrapSecuritySecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:        solrCloud.SecurityBootstrapSecretName(),
+			Namespace:   solrCloud.GetNamespace(),
+			Labels:      labels,
+			Annotations: annotations,
+		},
+		Data: map[string][]byte{
+			"admin":          securityBootstrapInfo["admin"],
+			"solr":           securityBootstrapInfo["solr"],
+			SecurityJsonFile: securityBootstrapInfo[SecurityJsonFile],
+		},
+		Type: corev1.SecretTypeOpaque,
+	}
+
+	return basicAuthSecret, boostrapSecuritySecret
+}
+
+func generateSecurityJson(solrCloud *solr.SolrCloud) map[string][]byte {
+	blockUnknown := true
+
+	probeRole := "\"k8s\"" // probe endpoints are secures
+	if !solrCloud.Spec.SolrSecurity.ProbesRequireAuth {
+		blockUnknown = false
+		probeRole = "null" // a JSON null value here to allow open access
+	}
+
+	probeAuthz := ""
+	for i, p := range getProbePaths(solrCloud) {
+		if i > 0 {
+			probeAuthz += ", "
+		}
+		if strings.HasPrefix(p, "/solr") {
+			p = p[len("/solr"):]
+		}
+		probeAuthz += fmt.Sprintf("{ \"name\": \"k8s-probe-%d\", \"role\":%s, \"collection\": null, \"path\":\"%s\" }", i, probeRole, p)
+	}
+
+	// Create the user accounts for security.json with random passwords
+	// hashed with random salt, just as Solr's hashing works
+	username := solr.DefaultBasicAuthUsername
+	users := []string{"admin", username, "solr"}
+	secretData := make(map[string][]byte, len(users))
+	credentials := make(map[string]string, len(users))
+	for _, u := range users {
+		secretData[u] = randomPassword()
+		credentials[u] = solrPasswordHash(secretData[u])
+	}
+	credentialsJson, _ := json.Marshal(credentials)
+
+	securityJson := fmt.Sprintf(`{
+      "authentication":{
+        "blockUnknown": %t,
+        "class":"solr.BasicAuthPlugin",
+        "credentials": %s,
+        "realm":"Solr Basic Auth",
+        "forwardCredentials": false
+      },
+      "authorization": {
+        "class": "solr.RuleBasedAuthorizationPlugin",
+        "user-role": {
+          "admin": ["admin", "k8s"],
+          "%s": ["k8s"],
+          "solr": ["users", "k8s"]
+        },
+        "permissions": [
+          %s,
+          { "name": "k8s-status", "role":"k8s", "collection": null, "path":"/admin/collections" },
+          { "name": "k8s-metrics", "role":"k8s", "collection": null, "path":"/admin/metrics" },
+          { "name": "k8s-zk", "role":"k8s", "collection": null, "path":"/admin/zookeeper/status" },
+          { "name": "k8s-ping", "role":"k8s", "collection": "*", "path":"/admin/ping" },
+          { "name": "read", "role":["admin","users"] },
+          { "name": "update", "role":["admin"] },
+          { "name": "security-read", "role": ["admin"] },
+          { "name": "security-edit", "role": ["admin"] },
+          { "name": "all", "role":["admin"] }
+        ]
+      }
+    }`, blockUnknown, credentialsJson, username, probeAuthz)
+
+	// we need to store the security.json in the secret, otherwise we'd recompute it for every reconcile loop
+	// but that doesn't work for randomized passwords ...
+	secretData[SecurityJsonFile] = []byte(securityJson)
+
+	return secretData
+}
+
+func randomPassword() []byte {
+	rand.Seed(time.Now().UnixNano())
+	lower := "abcdefghijklmnpqrstuvwxyz" // no 'o'
+	upper := strings.ToUpper(lower)
+	digits := "0123456789"
+	chars := lower + upper + digits + "()[]%#@-()[]%#@-"
+	pass := make([]byte, 16)
+	// start with a lower char and end with an upper
+	pass[0] = lower[rand.Intn(len(lower))]
+	pass[len(pass)-1] = upper[rand.Intn(len(upper))]
+	perm := rand.Perm(len(chars))
+	for i := 1; i < len(pass)-1; i++ {
+		pass[i] = chars[perm[i]]
+	}
+	return pass
+}
+
+func randomSaltHash() []byte {
+	b := make([]byte, 32)
+	rand.Read(b)
+	salt := sha256.Sum256(b)
+	return salt[:]
+}
+
+// this mimics the password hash generation approach used by Solr
+func solrPasswordHash(passBytes []byte) string {
+	// combine password with salt to create the hash
+	salt := randomSaltHash()
+	passHashBytes := sha256.Sum256(append(salt[:], passBytes...))
+	passHashBytes = sha256.Sum256(passHashBytes[:])
+	passHash := b64.StdEncoding.EncodeToString(passHashBytes[:])
+	return fmt.Sprintf("%s %s", passHash, b64.StdEncoding.EncodeToString(salt))
+}
+
+// Gets a list of probe paths we need to setup authz for
+func getProbePaths(solrCloud *solr.SolrCloud) []string {
+	probePaths := []string{DefaultProbePath}
+	probePaths = append(probePaths, GetCustomProbePaths(solrCloud)...)
+	return uniqueProbePaths(probePaths)
+}
+
+func uniqueProbePaths(paths []string) []string {
+	keys := make(map[string]bool)
+	var set []string
+	for _, name := range paths {
+		if _, exists := keys[name]; !exists {
+			keys[name] = true
+			set = append(set, name)
+		}
+	}
+	return set
+}
+
+func secureProbeVolumeAndMount(secretName string) (*corev1.Volume, *corev1.VolumeMount) {
+	vol := &corev1.Volume{
+		Name: strings.ReplaceAll(secretName, ".", "-"),
+		VolumeSource: corev1.VolumeSource{
+			Secret: &corev1.SecretVolumeSource{
+				SecretName:  secretName,
+				DefaultMode: &SecretReadOnlyPermissions,
+			},
+		},
+	}
+	volMount := &corev1.VolumeMount{Name: vol.Name, MountPath: fmt.Sprintf("/etc/secrets/%s", vol.Name)}
+	return vol, volMount
+}
+
+// When running with TLS and clientAuth=Need or if the probe endpoints require auth, we need to use a command instead of HTTP Get
+// This function builds the custom probe command and returns any associated volume / mounts needed for the auth secrets
+func useSecureProbe(solrCloud *solr.SolrCloud, probe *corev1.Probe, mountPath string) {
+	// mount the secret in a file so it gets updated; env vars do not see:
+	// https://kubernetes.io/docs/concepts/configuration/secret/#environment-variables-are-not-updated-after-a-secret-update
+	basicAuthOption := ""
+	enableBasicAuth := ""
+	if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.ProbesRequireAuth {
+		usernameFile := fmt.Sprintf("%s/%s", mountPath, corev1.BasicAuthUsernameKey)
+		passwordFile := fmt.Sprintf("%s/%s", mountPath, corev1.BasicAuthPasswordKey)
+		basicAuthOption = fmt.Sprintf("-Dbasicauth=$(cat %s):$(cat %s)", usernameFile, passwordFile)
+		enableBasicAuth = " -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory "
+	}
+
+	// Is TLS enabled? If so we need some additional SSL related props
+	tlsJavaToolOpts, tlsJavaSysProps := secureProbeTLSJavaToolOpts(solrCloud)
+	javaToolOptions := strings.TrimSpace(basicAuthOption + " " + tlsJavaToolOpts)
+
+	// construct the probe command to invoke the SolrCLI "api" action
+	//
+	// and yes, this is ugly, but bin/solr doesn't expose the "api" action (as of 8.8.0) so we have to invoke java directly
+	// taking some liberties on the /opt/solr path based on the official Docker image as there is no ENV var set for that path
+	probeCommand := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"%s\" java %s %s "+
+		"-Dsolr.install.dir=\"/opt/solr\" -Dlog4j.configurationFile=\"/opt/solr/server/resources/log4j2-console.xml\" "+
+		"-classpath \"/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/*:/opt/solr/server/lib/ext/*:/opt/solr/server/lib/*\" "+
+		"org.apache.solr.util.SolrCLI api -get %s://localhost:%d%s",
+		javaToolOptions, tlsJavaSysProps, enableBasicAuth, solrCloud.UrlScheme(false), probe.HTTPGet.Port.IntVal, probe.HTTPGet.Path)
+	probeCommand = regexp.MustCompile(`\s+`).ReplaceAllString(strings.TrimSpace(probeCommand), " ")
+
+	// use an Exec instead of an HTTP GET
+	probe.HTTPGet = nil
+	probe.Exec = &corev1.ExecAction{Command: []string{"sh", "-c", probeCommand}}
+
+	// minimum of 5 seconds for exec probes as they are slow to initialize
+	if probe.TimeoutSeconds < 5 {
+		probe.TimeoutSeconds = 5
+	}
+}
diff --git a/controllers/util/solr_tls_util.go b/controllers/util/solr_tls_util.go
index a87993c..14d9251 100644
--- a/controllers/util/solr_tls_util.go
+++ b/controllers/util/solr_tls_util.go
@@ -722,8 +722,7 @@ func mountedTLSPath(dir *solr.MountedTLSDirectory, fileName string, defaultName
 
 // Command to set the urlScheme cluster prop to "https"
 func setUrlSchemeClusterPropCmd() string {
-	return "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}" +
-		"; /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd clusterprop -name urlScheme -val https" +
+	return "/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd clusterprop -name urlScheme -val https" +
 		"; /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd get /clusterprops.json;"
 }
 
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index b73670c..e2d9c8a 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -18,23 +18,16 @@
 package util
 
 import (
-	"crypto/sha256"
-	b64 "encoding/base64"
-	"encoding/json"
 	"fmt"
-	"math/rand"
-	"regexp"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
 	solr "github.com/apache/solr-operator/api/v1beta1"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	netv1 "k8s.io/api/networking/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+	"sort"
+	"strconv"
+	"strings"
 )
 
 const (
@@ -56,9 +49,6 @@ const (
 	SolrXmlFile                      = "solr.xml"
 	LogXmlMd5Annotation              = "solr.apache.org/logXmlMd5"
 	LogXmlFile                       = "log4j2.xml"
-	SecurityJsonFile                 = "security.json"
-	BasicAuthMd5Annotation           = "solr.apache.org/basicAuthMd5"
-	DefaultProbePath                 = "/admin/info/system"
 
 	DefaultStatefulSetPodManagementPolicy = appsv1.ParallelPodManagement
 )
@@ -68,7 +58,7 @@ const (
 // replicas: the number of replicas for the SolrCloud instance
 // storage: the size of the storage for the SolrCloud instance (e.g. 100Gi)
 // zkConnectionString: the connectionString of the ZK instance to connect to
-func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, hostNameIPs map[string]string, reconcileConfigInfo map[string]string, tls *TLSCerts) *appsv1.StatefulSet {
+func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, hostNameIPs map[string]string, reconcileConfigInfo map[string]string, tls *TLSCerts, security *SecurityConfig) *appsv1.StatefulSet {
 	terminationGracePeriod := int64(60)
 	solrPodPort := solrCloud.Spec.SolrAddressability.PodPort
 	fsGroup := int64(DefaultSolrGroup)
@@ -357,19 +347,6 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl
 		}
 	}
 
-	if (tls != nil && tls.ServerConfig != nil && tls.ServerConfig.Options.ClientAuth != solr.None) || (solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.ProbesRequireAuth) {
-		probeCommand, vol, volMount := configureSecureProbeCommand(solrCloud, defaultHandler.HTTPGet)
-		if vol != nil {
-			solrVolumes = append(solrVolumes, *vol)
-		}
-		if volMount != nil {
-			volumeMounts = append(volumeMounts, *volMount)
-		}
-		// reset the defaultHandler for the probes to invoke the SolrCLI api action instead of HTTP
-		defaultHandler = corev1.Handler{Exec: &corev1.ExecAction{Command: []string{"sh", "-c", probeCommand}}}
-		defaultProbeTimeout = 5
-	}
-
 	// track the MD5 of the custom solr.xml in the pod spec annotations,
 	// so we get a rolling restart when the configMap changes
 	if reconcileConfigInfo[SolrXmlMd5Annotation] != "" {
@@ -389,7 +366,7 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl
 		Value: strings.Join(allSolrOpts, " "),
 	})
 
-	initContainers := generateSolrSetupInitContainers(solrCloud, solrCloudStatus, solrDataVolumeName, reconcileConfigInfo)
+	initContainers := generateSolrSetupInitContainers(solrCloud, solrCloudStatus, solrDataVolumeName, security)
 
 	// Add user defined additional init containers
 	if customPodOptions != nil && len(customPodOptions.InitContainers) > 0 {
@@ -563,10 +540,15 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl
 		tls.enableTLSOnSolrCloudStatefulSet(stateful)
 	}
 
+	// If probes require auth is set OR tls is configured to want / need client auth, then reconfigure the probes to use an exec
+	if (solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.ProbesRequireAuth) || (tls != nil && tls.ServerConfig != nil && tls.ServerConfig.Options.ClientAuth != solr.None) {
+		enableSecureProbesOnSolrCloudStatefulSet(solrCloud, stateful)
+	}
+
 	return stateful
 }
 
-func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, solrDataVolumeName string, reconcileConfigInfo map[string]string) (containers []corev1.Container) {
+func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, solrDataVolumeName string, security *SecurityConfig) (containers []corev1.Container) {
 	// The setup of the solr.xml will always be necessary
 	volumeMounts := []corev1.VolumeMount{
 		{
@@ -605,7 +587,7 @@ func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus
 
 	containers = append(containers, volumePrepInitContainer)
 
-	if hasZKSetupContainer, zkSetupContainer := generateZKInteractionInitContainer(solrCloud, solrCloudStatus, reconcileConfigInfo); hasZKSetupContainer {
+	if hasZKSetupContainer, zkSetupContainer := generateZKInteractionInitContainer(solrCloud, solrCloudStatus, security); hasZKSetupContainer {
 		containers = append(containers, zkSetupContainer)
 	}
 
@@ -987,11 +969,11 @@ func CreateNodeIngressRule(solrCloud *solr.SolrCloud, nodeName string, domainNam
 }
 
 // TODO: Have this replace the postStart hook for creating the chroot
-func generateZKInteractionInitContainer(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, reconcileConfigInfo map[string]string) (bool, corev1.Container) {
+func generateZKInteractionInitContainer(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, security *SecurityConfig) (bool, corev1.Container) {
 	allSolrOpts := make([]string, 0)
 
 	// Add all necessary ZK Info
-	envVars, zkSolrOpt, _ := createZkConnectionEnvVars(solrCloud, solrCloudStatus)
+	envVars, zkSolrOpt, hasChroot := createZkConnectionEnvVars(solrCloud, solrCloudStatus)
 	if zkSolrOpt != "" {
 		allSolrOpts = append(allSolrOpts, zkSolrOpt)
 	}
@@ -1010,21 +992,21 @@ func generateZKInteractionInitContainer(solrCloud *solr.SolrCloud, solrCloudStat
 
 	cmd := ""
 
+	if hasChroot {
+		cmd += "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}; "
+	}
+
 	if solrCloud.Spec.SolrTLS != nil {
-		cmd = setUrlSchemeClusterPropCmd()
+		cmd += setUrlSchemeClusterPropCmd()
 	}
 
-	if reconcileConfigInfo[SecurityJsonFile] != "" {
+	if security != nil && security.SecurityJson != "" {
 		envVars = append(envVars, corev1.EnvVar{Name: "SECURITY_JSON", ValueFrom: &corev1.EnvVarSource{
 			SecretKeyRef: &corev1.SecretKeySelector{
 				LocalObjectReference: corev1.LocalObjectReference{Name: solrCloud.SecurityBootstrapSecretName()},
 				Key:                  SecurityJsonFile}}})
 
-		if cmd == "" {
-			cmd += "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}; "
-		}
-		cmd += "ZK_SECURITY_JSON=$(/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd get /security.json); "
-		cmd += "if [ ${#ZK_SECURITY_JSON} -lt 3 ]; then echo $SECURITY_JSON > /tmp/security.json; /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd putfile /security.json /tmp/security.json; echo \"put security.json in ZK\"; fi"
+		cmd += cmdToPutSecurityJsonInZk()
 	}
 
 	if cmd != "" {
@@ -1105,140 +1087,6 @@ func setupVolumeMountForUserProvidedConfigMapEntry(reconcileConfigInfo map[strin
 	return &corev1.VolumeMount{Name: volName, MountPath: mountPath}, &corev1.EnvVar{Name: envVar, Value: pathToFile}, vol
 }
 
-func BasicAuthHeader(basicAuthSecret *corev1.Secret) string {
-	creds := fmt.Sprintf("%s:%s", basicAuthSecret.Data[corev1.BasicAuthUsernameKey], basicAuthSecret.Data[corev1.BasicAuthPasswordKey])
-	return "Basic " + b64.StdEncoding.EncodeToString([]byte(creds))
-}
-
-func ValidateBasicAuthSecret(basicAuthSecret *corev1.Secret) error {
-	if basicAuthSecret.Type != corev1.SecretTypeBasicAuth {
-		return fmt.Errorf("invalid secret type %v; user-provided secret %s must be of type: %v",
-			basicAuthSecret.Type, basicAuthSecret.Name, corev1.SecretTypeBasicAuth)
-	}
-
-	if _, ok := basicAuthSecret.Data[corev1.BasicAuthUsernameKey]; !ok {
-		return fmt.Errorf("%s key not found in user-provided basic-auth secret %s",
-			corev1.BasicAuthUsernameKey, basicAuthSecret.Name)
-	}
-
-	if _, ok := basicAuthSecret.Data[corev1.BasicAuthPasswordKey]; !ok {
-		return fmt.Errorf("%s key not found in user-provided basic-auth secret %s",
-			corev1.BasicAuthPasswordKey, basicAuthSecret.Name)
-	}
-
-	return nil
-}
-
-func GenerateBasicAuthSecretWithBootstrap(solrCloud *solr.SolrCloud) (*corev1.Secret, *corev1.Secret) {
-
-	securityBootstrapInfo := generateSecurityJson(solrCloud)
-
-	labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
-	var annotations map[string]string
-	basicAuthSecret := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:        solrCloud.BasicAuthSecretName(),
-			Namespace:   solrCloud.GetNamespace(),
-			Labels:      labels,
-			Annotations: annotations,
-		},
-		Data: map[string][]byte{
-			corev1.BasicAuthUsernameKey: []byte(solr.DefaultBasicAuthUsername),
-			corev1.BasicAuthPasswordKey: securityBootstrapInfo[solr.DefaultBasicAuthUsername],
-		},
-		Type: corev1.SecretTypeBasicAuth,
-	}
-
-	// this secret holds the admin and solr user credentials and the security.json needed to bootstrap Solr security
-	// once the security.json is created using the setup-zk initContainer, it is not updated by the operator
-	boostrapSecuritySecret := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:        solrCloud.SecurityBootstrapSecretName(),
-			Namespace:   solrCloud.GetNamespace(),
-			Labels:      labels,
-			Annotations: annotations,
-		},
-		Data: map[string][]byte{
-			"admin":          securityBootstrapInfo["admin"],
-			"solr":           securityBootstrapInfo["solr"],
-			SecurityJsonFile: securityBootstrapInfo[SecurityJsonFile],
-		},
-		Type: corev1.SecretTypeOpaque,
-	}
-
-	return basicAuthSecret, boostrapSecuritySecret
-}
-
-func generateSecurityJson(solrCloud *solr.SolrCloud) map[string][]byte {
-	blockUnknown := true
-
-	probeRole := "\"k8s\"" // probe endpoints are secures
-	if !solrCloud.Spec.SolrSecurity.ProbesRequireAuth {
-		blockUnknown = false
-		probeRole = "null" // a JSON null value here to allow open access
-	}
-
-	probePaths := getProbePaths(solrCloud)
-	probeAuthz := ""
-	for i, p := range probePaths {
-		if i > 0 {
-			probeAuthz += ", "
-		}
-		if strings.HasPrefix(p, "/solr") {
-			p = p[len("/solr"):]
-		}
-		probeAuthz += fmt.Sprintf("{ \"name\": \"k8s-probe-%d\", \"role\":%s, \"collection\": null, \"path\":\"%s\" }", i, probeRole, p)
-	}
-
-	// Create the user accounts for security.json with random passwords
-	// hashed with random salt, just as Solr's hashing works
-	username := solr.DefaultBasicAuthUsername
-	users := []string{"admin", username, "solr"}
-	secretData := make(map[string][]byte, len(users))
-	credentials := make(map[string]string, len(users))
-	for _, u := range users {
-		secretData[u] = randomPassword()
-		credentials[u] = solrPasswordHash(secretData[u])
-	}
-	credentialsJson, _ := json.Marshal(credentials)
-
-	securityJson := fmt.Sprintf(`{
-      "authentication":{
-        "blockUnknown": %t,
-        "class":"solr.BasicAuthPlugin",
-        "credentials": %s,
-        "realm":"Solr Basic Auth",
-        "forwardCredentials": false
-      },
-      "authorization": {
-        "class": "solr.RuleBasedAuthorizationPlugin",
-        "user-role": {
-          "admin": ["admin", "k8s"],
-          "%s": ["k8s"],
-          "solr": ["users", "k8s"]
-        },
-        "permissions": [
-          %s,
-          { "name": "k8s-status", "role":"k8s", "collection": null, "path":"/admin/collections" },
-          { "name": "k8s-metrics", "role":"k8s", "collection": null, "path":"/admin/metrics" },
-          { "name": "k8s-zk", "role":"k8s", "collection": null, "path":"/admin/zookeeper/status" },
-          { "name": "k8s-ping", "role":"k8s", "collection": "*", "path":"/admin/ping" },
-          { "name": "read", "role":["admin","users"] },
-          { "name": "update", "role":["admin"] },
-          { "name": "security-read", "role": ["admin"] },
-          { "name": "security-edit", "role": ["admin"] },
-          { "name": "all", "role":["admin"] }
-        ]
-      }
-    }`, blockUnknown, credentialsJson, username, probeAuthz)
-
-	// we need to store the security.json in the secret, otherwise we'd recompute it for every reconcile loop
-	// but that doesn't work for randomized passwords ...
-	secretData[SecurityJsonFile] = []byte(securityJson)
-
-	return secretData
-}
-
 func GetCustomProbePaths(solrCloud *solr.SolrCloud) []string {
 	probePaths := []string{}
 
@@ -1262,102 +1110,3 @@ func GetCustomProbePaths(solrCloud *solr.SolrCloud) []string {
 
 	return probePaths
 }
-
-// Gets a list of probe paths we need to setup authz for
-func getProbePaths(solrCloud *solr.SolrCloud) []string {
-	probePaths := []string{DefaultProbePath}
-	probePaths = append(probePaths, GetCustomProbePaths(solrCloud)...)
-	return uniqueProbePaths(probePaths)
-}
-
-func randomPassword() []byte {
-	rand.Seed(time.Now().UnixNano())
-	lower := "abcdefghijklmnpqrstuvwxyz" // no 'o'
-	upper := strings.ToUpper(lower)
-	digits := "0123456789"
-	chars := lower + upper + digits + "()[]%#@-()[]%#@-"
-	pass := make([]byte, 16)
-	// start with a lower char and end with an upper
-	pass[0] = lower[rand.Intn(len(lower))]
-	pass[len(pass)-1] = upper[rand.Intn(len(upper))]
-	perm := rand.Perm(len(chars))
-	for i := 1; i < len(pass)-1; i++ {
-		pass[i] = chars[perm[i]]
-	}
-	return pass
-}
-
-func randomSaltHash() []byte {
-	b := make([]byte, 32)
-	rand.Read(b)
-	salt := sha256.Sum256(b)
-	return salt[:]
-}
-
-// this mimics the password hash generation approach used by Solr
-func solrPasswordHash(passBytes []byte) string {
-	// combine password with salt to create the hash
-	salt := randomSaltHash()
-	passHashBytes := sha256.Sum256(append(salt[:], passBytes...))
-	passHashBytes = sha256.Sum256(passHashBytes[:])
-	passHash := b64.StdEncoding.EncodeToString(passHashBytes[:])
-	return fmt.Sprintf("%s %s", passHash, b64.StdEncoding.EncodeToString(salt))
-}
-
-func uniqueProbePaths(paths []string) []string {
-	keys := make(map[string]bool)
-	var set []string
-	for _, name := range paths {
-		if _, exists := keys[name]; !exists {
-			keys[name] = true
-			set = append(set, name)
-		}
-	}
-	return set
-}
-
-// When running with TLS and clientAuth=Need or if the probe endpoints require auth, we need to use a command instead of HTTP Get
-// This function builds the custom probe command and returns any associated volume / mounts needed for the auth secrets
-func configureSecureProbeCommand(solrCloud *solr.SolrCloud, defaultProbeGetAction *corev1.HTTPGetAction) (string, *corev1.Volume, *corev1.VolumeMount) {
-	// mount the secret in a file so it gets updated; env vars do not see:
-	// https://kubernetes.io/docs/concepts/configuration/secret/#environment-variables-are-not-updated-after-a-secret-update
-	basicAuthOption := ""
-	enableBasicAuth := ""
-	var volMount *corev1.VolumeMount
-	var vol *corev1.Volume
-	if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.ProbesRequireAuth {
-		secretName := solrCloud.BasicAuthSecretName()
-		vol = &corev1.Volume{
-			Name: strings.ReplaceAll(secretName, ".", "-"),
-			VolumeSource: corev1.VolumeSource{
-				Secret: &corev1.SecretVolumeSource{
-					SecretName:  secretName,
-					DefaultMode: &SecretReadOnlyPermissions,
-				},
-			},
-		}
-		mountPath := fmt.Sprintf("/etc/secrets/%s", vol.Name)
-		volMount = &corev1.VolumeMount{Name: vol.Name, MountPath: mountPath}
-		usernameFile := fmt.Sprintf("%s/%s", mountPath, corev1.BasicAuthUsernameKey)
-		passwordFile := fmt.Sprintf("%s/%s", mountPath, corev1.BasicAuthPasswordKey)
-		basicAuthOption = fmt.Sprintf("-Dbasicauth=$(cat %s):$(cat %s)", usernameFile, passwordFile)
-		enableBasicAuth = " -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory "
-	}
-
-	// Is TLS enabled? If so we need some additional SSL related props
-	tlsJavaToolOpts, tlsJavaSysProps := secureProbeTLSJavaToolOpts(solrCloud)
-	javaToolOptions := strings.TrimSpace(basicAuthOption + " " + tlsJavaToolOpts)
-
-	// construct the probe command to invoke the SolrCLI "api" action
-	//
-	// and yes, this is ugly, but bin/solr doesn't expose the "api" action (as of 8.8.0) so we have to invoke java directly
-	// taking some liberties on the /opt/solr path based on the official Docker image as there is no ENV var set for that path
-	probeCommand := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"%s\" java %s %s "+
-		"-Dsolr.install.dir=\"/opt/solr\" -Dlog4j.configurationFile=\"/opt/solr/server/resources/log4j2-console.xml\" "+
-		"-classpath \"/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/*:/opt/solr/server/lib/ext/*:/opt/solr/server/lib/*\" "+
-		"org.apache.solr.util.SolrCLI api -get %s://localhost:%d%s",
-		javaToolOptions, tlsJavaSysProps, enableBasicAuth, solrCloud.UrlScheme(false), defaultProbeGetAction.Port.IntVal, defaultProbeGetAction.Path)
-	probeCommand = regexp.MustCompile(`\s+`).ReplaceAllString(strings.TrimSpace(probeCommand), " ")
-
-	return probeCommand, vol, volMount
-}