This is an automated email from the ASF dual-hosted git repository. gerlowskija 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 6bd0fce Migrate from zkcli.sh to 'solr zk' CLI tool (#738) 6bd0fce is described below commit 6bd0fce03445dfffb66bd8d646b85864c287fe4a Author: Michael <35507196+mcarro...@users.noreply.github.com> AuthorDate: Mon Jan 6 14:44:01 2025 -0500 Migrate from zkcli.sh to 'solr zk' CLI tool (#738) zkcli.sh is deprecated and will be removed in Solr 10. Our existing use of it also wasn't quite flexible enough to gracefully handle missing "security.json" nodes. Migrating to "bin/solr zk" in our "setup-zk" initContainer fixes both of these issues. Closes #720 Closes #731 --------- Co-authored-by: Jason Gerlowski <gerlowsk...@apache.org> --- .../solrcloud_controller_basic_auth_test.go | 11 ++- controllers/util/solr_security_util.go | 18 ++++- docs/development.md | 5 ++ helm/solr-operator/Chart.yaml | 9 +++ tests/e2e/solrcloud_security_json_test.go | 69 +++++++++++++++++ tests/e2e/test_utils_test.go | 88 ++++++++++++++++++++++ 6 files changed, 192 insertions(+), 8 deletions(-) diff --git a/controllers/solrcloud_controller_basic_auth_test.go b/controllers/solrcloud_controller_basic_auth_test.go index cd3fbf5..c271393 100644 --- a/controllers/solrcloud_controller_basic_auth_test.go +++ b/controllers/solrcloud_controller_basic_auth_test.go @@ -351,10 +351,13 @@ func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1bet func expectPutSecurityJsonInZkCmd(g Gomega, expInitContainer *corev1.Container) { g.Expect(expInitContainer).To(Not(BeNil()), "Didn't find the setup-zk InitContainer in the sts!") - expCmd := "ZK_SECURITY_JSON=$(/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd get /security.json || echo 'failed-to-get-security.json'); " + - "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" + expCmd := "solr zk cp zk:/security.json /tmp/current_security.json >/dev/null 2>&1; " + + "GET_CURRENT_SECURITY_JSON_EXIT_CODE=$?; if [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 0 ]; then " + + "if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' /tmp/current_security.json ]; then " + + "echo $SECURITY_JSON > /tmp/security.json; solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " + + " echo 'Blank security.json found. Put new security.json in ZK'; fi; elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " + + " echo $SECURITY_JSON > /tmp/security.json; solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " + + " echo 'No security.json found. Put new security.json in ZK'; fi" g.Expect(expInitContainer.Command[2]).To(ContainSubstring(expCmd), "setup-zk initContainer not configured to bootstrap security.json!") } diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go index 97f7e60..2eb8679 100644 --- a/controllers/util/solr_security_util.go +++ b/controllers/util/solr_security_util.go @@ -237,10 +237,20 @@ func addHostHeaderToProbe(httpGet *corev1.HTTPGetAction, host string) { } func cmdToPutSecurityJsonInZk() string { - scriptsDir := "/opt/solr/server/scripts/cloud-scripts" - cmd := " ZK_SECURITY_JSON=$(%s/zkcli.sh -zkhost ${ZK_HOST} -cmd get /security.json || echo 'failed-to-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) + cmd := " solr zk cp zk:/security.json /tmp/current_security.json >/dev/null 2>&1; " + + " GET_CURRENT_SECURITY_JSON_EXIT_CODE=$?; " + + "if [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 0 ]; then " + // JSON already exists + "if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' /tmp/current_security.json ]; then " + // File doesn't exist, is empty, or is just '{}' + " echo $SECURITY_JSON > /tmp/security.json;" + + " solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " + + " echo 'Blank security.json found. Put new security.json in ZK'; " + + "fi; " + // TODO: Consider checking a diff and still applying over the top + "elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " + // JSON doesn't exist, but not other error types + " echo $SECURITY_JSON > /tmp/security.json;" + + " solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " + + " echo 'No security.json found. Put new security.json in ZK'; " + + "fi" + return cmd } // Add auth data to the supplied Context using secrets already resolved (stored in the SecurityConfig) diff --git a/docs/development.md b/docs/development.md index f8d005a..cf9907c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -55,6 +55,11 @@ export PATH="$PATH:$GOPATH/bin" # You likely want to add this line to your ~/.ba make install-dependencies ``` +Note: if you have previously installed/older versions of dependencies, you can first clear these dependencies with: +```bash +make clean +``` + ## Build the Solr CRDs If you have changed anything in the [APIs directory](/api/v1beta1), you will need to run the following command to regenerate all Solr CRDs. diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml index af4b173..44ca43e 100644 --- a/helm/solr-operator/Chart.yaml +++ b/helm/solr-operator/Chart.yaml @@ -103,6 +103,15 @@ annotations: url: https://github.com/apache/solr-operator/issues/682 - name: Github PR url: https://github.com/apache/solr-operator/pull/692 + - kind: fixed + description: setup-zk initContainer now gracefully handles absent security.json on initial upload. + links: + - name: Github Issue + url: https://github.com/apache/solr-operator/issues/720 + - name: Additional Github Issue + url: https://github.com/apache/solr-operator/issues/731 + - name: Github PR + url: https://github.com/apache/solr-operator/pull/738 artifacthub.io/images: | - name: solr-operator image: apache/solr-operator:v0.9.0-prerelease diff --git a/tests/e2e/solrcloud_security_json_test.go b/tests/e2e/solrcloud_security_json_test.go new file mode 100644 index 0000000..1ceef5e --- /dev/null +++ b/tests/e2e/solrcloud_security_json_test.go @@ -0,0 +1,69 @@ +/* + * 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 e2e + +import ( + "context" + solrv1beta1 "github.com/apache/solr-operator/api/v1beta1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/utils/pointer" +) + +var _ = FDescribe("E2E - SolrCloud - Security JSON", func() { + var ( + solrCloud *solrv1beta1.SolrCloud + ) + + BeforeEach(func() { + solrCloud = generateBaseSolrCloudWithSecurityJSON(1) + }) + + JustBeforeEach(func(ctx context.Context) { + By("generating the security.json secret and basic auth secret") + generateSolrSecuritySecret(ctx, solrCloud) + generateSolrBasicAuthSecret(ctx, solrCloud) + + By("creating the SolrCloud") + Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) + + DeferCleanup(func(ctx context.Context) { + cleanupTest(ctx, solrCloud) + }) + + By("Waiting for the SolrCloud to come up healthy") + solrCloud = expectSolrCloudToBeReady(ctx, solrCloud) + + By("creating a first Solr Collection") + createAndQueryCollection(ctx, solrCloud, "basic", 1, 1) + }) + + FContext("Provided Zookeeper", func() { + BeforeEach(func() { + solrCloud.Spec.ZookeeperRef = &solrv1beta1.ZookeeperRef{ + ProvidedZookeeper: &solrv1beta1.ZookeeperSpec{ + Replicas: pointer.Int32(1), + Ephemeral: &solrv1beta1.ZKEphemeral{}, + }, + } + }) + + // All testing will be done in the "JustBeforeEach" logic, no additional tests required here + FIt("Starts correctly", func(ctx context.Context) {}) + }) +}) diff --git a/tests/e2e/test_utils_test.go b/tests/e2e/test_utils_test.go index 95677d3..203b93e 100644 --- a/tests/e2e/test_utils_test.go +++ b/tests/e2e/test_utils_test.go @@ -581,6 +581,94 @@ func generateBaseSolrCloud(replicas int) *solrv1beta1.SolrCloud { } } +// Uses default password from docs : SolrRocks +// The hash is generated as: base64(sha256(sha256(salt+password))) base64(salt)) +// See https://solr.apache.org/guide/solr/latest/deployment-guide/basic-authentication-plugin.html +func generateSolrSecuritySecret(ctx context.Context, solrCloud *solrv1beta1.SolrCloud) { + securityJsonSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrCloud.Name + "-security-secret", + Namespace: solrCloud.Namespace, + }, + StringData: map[string]string{ + "security.json": `{ + "authentication": { + "blockUnknown": false, + "class": "solr.BasicAuthPlugin", + "credentials": { + "test-oper": "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c=" + }, + "realm": "Solr Basic Auth", + "forwardCredentials": false + }, + "authorization": { + "class": "solr.RuleBasedAuthorizationPlugin", + "user-role": { + "test-oper": "test-oper" + }, + "permissions": [ + { + "name": "cluster", + "role": null + }, + { + "name": "collections", + "role": null, + "collection": "*" + } + ] + } + }`, + }, + Type: corev1.SecretTypeOpaque, + } + Expect(k8sClient.Create(ctx, securityJsonSecret)).To(Succeed(), "Failed to create secret for security json in namespace "+solrCloud.Namespace) + + expectSecret(ctx, solrCloud, securityJsonSecret.Name) + return +} + +func generateSolrBasicAuthSecret(ctx context.Context, solrCloud *solrv1beta1.SolrCloud) { + basicAuthSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrCloud.Name + "-basic-auth-secret", + Namespace: solrCloud.Namespace, + }, + // Using default creds + StringData: map[string]string{ + "username": "test-oper", + "password": "SolrRocks", + }, + Type: corev1.SecretTypeBasicAuth, + } + Expect(k8sClient.Create(ctx, basicAuthSecret)).To(Succeed(), "Failed to create secret for basic auth in namespace "+solrCloud.Namespace) + + expectSecret(ctx, solrCloud, basicAuthSecret.Name) + return +} + +func generateBaseSolrCloudWithSecurityJSON(replicas int) *solrv1beta1.SolrCloud { + solrCloud := generateBaseSolrCloud(replicas) + + // Ensure SolrSecurity is initialized + if solrCloud.Spec.SolrSecurity == nil { + solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{} + } + + solrCloud.Spec.SolrSecurity.BootstrapSecurityJson = &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: solrCloud.Name + "-security-secret", + }, + Key: "security.json", + } + + solrCloud.Spec.SolrSecurity.AuthenticationType = "Basic" + + solrCloud.Spec.SolrSecurity.BasicAuthSecret = solrCloud.Name + "-basic-auth-secret" + + return solrCloud +} + func generateBaseSolrCloudWithPlacementPolicy(replicas int, placementPlugin string) *solrv1beta1.SolrCloud { solrCloud := generateBaseSolrCloud(replicas) solrCloud.Spec.CustomSolrKubeOptions.PodOptions.EnvVariables = []corev1.EnvVar{