From 696faff012c924199fd85e505efe965ce3a8ab2c Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Tue, 19 Sep 2023 16:13:32 -0400 Subject: [PATCH] Support TLS with recent Solr 9.x versions (#610) - Add upgrade notes for minimum Solr version - SNI is handled by default in Solr 9.4, so don't worry about it - Lots of testing fixes - New environment variables are used for Solr Pods --- .../solrcloud_controller_backup_test.go | 18 +- .../solrcloud_controller_basic_auth_test.go | 27 +- .../solrcloud_controller_externaldns_test.go | 36 +- .../solrcloud_controller_ingress_test.go | 40 +- .../solrcloud_controller_storage_test.go | 22 +- controllers/solrcloud_controller_test.go | 34 +- controllers/solrcloud_controller_tls_test.go | 298 ++++++------ controllers/solrcloud_controller_zk_test.go | 38 +- .../solrprometheusexporter_controller_test.go | 53 ++- ...rprometheusexporter_controller_tls_test.go | 32 +- controllers/suite_test.go | 5 +- controllers/util/prometheus_exporter_util.go | 21 +- controllers/util/solr_security_util.go | 60 ++- controllers/util/solr_tls_util.go | 111 ++--- controllers/util/solr_util.go | 25 +- docs/upgrade-notes.md | 31 +- helm/solr-operator/Chart.yaml | 7 + helm/solr/Chart.yaml | 13 +- tests/e2e/prometheus_exporter_tls_test.go | 177 ++++++++ tests/e2e/solrcloud_tls_test.go | 405 ++--------------- tests/e2e/suite_test.go | 4 +- tests/e2e/test_utils_test.go | 3 +- tests/e2e/utils_tls_test.go | 425 ++++++++++++++++++ 23 files changed, 1107 insertions(+), 778 deletions(-) create mode 100644 tests/e2e/prometheus_exporter_tls_test.go create mode 100644 tests/e2e/utils_tls_test.go diff --git a/controllers/solrcloud_controller_backup_test.go b/controllers/solrcloud_controller_backup_test.go index ec8874c4..0e8cafda 100644 --- a/controllers/solrcloud_controller_backup_test.go +++ b/controllers/solrcloud_controller_backup_test.go @@ -32,14 +32,10 @@ import ( var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -49,7 +45,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -59,7 +55,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -89,7 +85,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr ConfigMap") configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLStringForCloud(solrCloud)}) @@ -107,7 +103,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -155,7 +151,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_LOG_LEVEL": "INFO", @@ -209,7 +205,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_LOG_LEVEL": "INFO", @@ -310,7 +306,7 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr ConfigMap") configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLStringForCloud(solrCloud)}) diff --git a/controllers/solrcloud_controller_basic_auth_test.go b/controllers/solrcloud_controller_basic_auth_test.go index bf071d41..79972123 100644 --- a/controllers/solrcloud_controller_basic_auth_test.go +++ b/controllers/solrcloud_controller_basic_auth_test.go @@ -32,14 +32,10 @@ import ( var _ = FDescribe("SolrCloud controller - Basic Auth", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - replicas := int32(1) solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ @@ -57,7 +53,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -67,7 +63,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -78,7 +74,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { ProbesRequireAuth: true, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectStatefulSetBasicAuthConfig(ctx, solrCloud, true) }) }) @@ -106,7 +102,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { ProbesRequireAuth: true, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectStatefulSetBasicAuthConfig(ctx, solrCloud, true) }) }) @@ -128,7 +124,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { PasswordKey: "read-only-pass", } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectStatefulSetBasicAuthConfig(ctx, solrCloud, true) }) }) @@ -139,7 +135,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { AuthenticationType: solrv1beta1.Basic, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("Testing that the statefulSet exists and has the correct information") expectStatefulSetBasicAuthConfig(ctx, solrCloud, true) @@ -161,7 +157,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { BasicAuthSecret: basicAuthSecretName, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("Making sure that no statefulSet exists until the BasicAuth Secret is created") expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -186,7 +182,7 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("Making sure that no statefulSet exists until the BasicAuth Secret is created") expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -286,11 +282,8 @@ func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1bet g.Expect(basicAuthSecretVolMount).To(Not(BeNil()), "No Basic Auth volume mount used in Solr container") g.Expect(basicAuthSecretVolMount.MountPath).To(Equal("/etc/secrets/"+secretName), "Wrong path used to mount Basic Auth volume") - expProbeCmd := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"-Dbasicauth=$(cat /etc/secrets/%s-solrcloud-basic-auth/username):$(cat /etc/secrets/%s-solrcloud-basic-auth/password)\" java "+ - "-Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory "+ - "-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 http://localhost:8983%s", + expProbeCmd := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"-Dbasicauth=$(cat /etc/secrets/%s-solrcloud-basic-auth/username):$(cat /etc/secrets/%s-solrcloud-basic-auth/password) -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory\" "+ + "solr api -get \"http://${POD_NAME}:8983%s\"", solrCloud.Name, solrCloud.Name, expProbePath) g.Expect(mainContainer.LivenessProbe).To(Not(BeNil()), "main container should have a liveness probe defined") g.Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when auth is enabled") diff --git a/controllers/solrcloud_controller_externaldns_test.go b/controllers/solrcloud_controller_externaldns_test.go index 2c9527fc..b63a1ffc 100644 --- a/controllers/solrcloud_controller_externaldns_test.go +++ b/controllers/solrcloud_controller_externaldns_test.go @@ -29,14 +29,10 @@ import ( var _ = FDescribe("SolrCloud controller - External DNS", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - replicas := int32(2) solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ @@ -64,7 +60,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -74,7 +70,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -90,7 +86,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { CommonServicePort: 4000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -102,7 +98,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "3000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -164,7 +160,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { CommonServicePort: 5000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("ensuring the SolrCloud resource is updated with correct specs") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults") @@ -182,7 +178,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "2000", "SOLR_NODE_PORT": "2000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -235,7 +231,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { CommonServicePort: 2000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("ensuring the SolrCloud resource is updated with correct specs") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults") @@ -253,7 +249,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "3000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -305,7 +301,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { CommonServicePort: 4000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("ensuring the SolrCloud resource is updated with correct specs") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults") @@ -323,7 +319,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "3000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -379,7 +375,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { CommonServicePort: 4000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -391,7 +387,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "3000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -453,7 +449,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { KubeDomain: testKubeDomain, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("ensuring the SolrCloud resource is updated with correct specs") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults") @@ -471,7 +467,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain, "SOLR_PORT": "2000", "SOLR_NODE_PORT": "2000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -524,7 +520,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { KubeDomain: testKubeDomain, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -536,7 +532,7 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain, "SOLR_PORT": "2000", "SOLR_NODE_PORT": "2000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", diff --git a/controllers/solrcloud_controller_ingress_test.go b/controllers/solrcloud_controller_ingress_test.go index 697bc8b7..f9c61ba6 100644 --- a/controllers/solrcloud_controller_ingress_test.go +++ b/controllers/solrcloud_controller_ingress_test.go @@ -31,15 +31,11 @@ import ( var _ = FDescribe("SolrCloud controller - Ingress", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) replicas := 3 BeforeEach(func() { - ctx = context.Background() - int32Replicas := int32(replicas) solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ @@ -76,7 +72,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -86,7 +82,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -103,7 +99,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { CommonServicePort: 4000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -118,7 +114,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain, + "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "100", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -178,7 +174,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { Controller: "acme.io/foo", }, } - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{ External: &solrv1beta1.ExternalAddressability{ Method: solrv1beta1.Ingress, @@ -195,11 +191,11 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { By("Create a default ingress class, so that the ingress is defaulted with this ingress class name") Expect(k8sClient.Create(ctx, ingressClass)).To(Succeed(), "Create a default ingress class for the ingress") }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { By("Deleting the ingress class, so other tests do not use the default") Expect(k8sClient.Delete(ctx, ingressClass)).To(Succeed(), "Delete the default ingress class") }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("ensuring the SolrCloud resource is updated with correct specs") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults") @@ -218,7 +214,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "3000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -282,7 +278,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { } solrCloud.Spec.CustomSolrKubeOptions.IngressOptions.IngressClassName = nil }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -297,7 +293,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain, + "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "100", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -362,7 +358,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { CommonServicePort: 4000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -374,7 +370,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "100", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -436,7 +432,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { CommonServicePort: 4000, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -451,7 +447,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain, + "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "100", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -512,7 +508,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { KubeDomain: testKubeDomain, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -524,7 +520,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + ".svc." + testKubeDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + ".svc." + testKubeDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "100", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -585,7 +581,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { KubeDomain: testKubeDomain, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -600,7 +596,7 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain, + "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain, "SOLR_PORT": "3000", "SOLR_NODE_PORT": "100", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", diff --git a/controllers/solrcloud_controller_storage_test.go b/controllers/solrcloud_controller_storage_test.go index 3b4c2a8a..d4664fad 100644 --- a/controllers/solrcloud_controller_storage_test.go +++ b/controllers/solrcloud_controller_storage_test.go @@ -31,14 +31,10 @@ import ( var _ = FDescribe("SolrCloud controller - Storage", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -65,7 +61,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -75,7 +71,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -93,7 +89,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when the reclaim policy is Retain") @@ -129,7 +125,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(1), "The solrcloud should have no finalizers when the reclaim policy is Retain") @@ -165,7 +161,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{}, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used") @@ -194,7 +190,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used") @@ -226,7 +222,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used") @@ -260,7 +256,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used") @@ -298,7 +294,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrCloud finalizers") expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used") diff --git a/controllers/solrcloud_controller_test.go b/controllers/solrcloud_controller_test.go index 52694de8..16460139 100644 --- a/controllers/solrcloud_controller_test.go +++ b/controllers/solrcloud_controller_test.go @@ -40,14 +40,10 @@ func newBoolPtr(value bool) *bool { var _ = FDescribe("SolrCloud controller - General", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -57,7 +53,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -67,7 +63,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -101,7 +97,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -122,7 +118,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_JAVA_MEM": "-Xmx4G", "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", @@ -237,7 +233,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr ConfigMap") configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLString("", []string{}, []string{})}) Expect(configMap.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testConfigMapLabels)), "Incorrect configMap labels") @@ -252,7 +248,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.") expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/test", - "SOLR_HOST": "$(POD_HOSTNAME).foo-solrcloud-headless.default", + "SOLR_HOST": "$(POD_NAME).foo-solrcloud-headless.default", "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "GC_TUNE": "gc Options", @@ -329,7 +325,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr Status values") expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) { g.Expect(found.ZookeeperConnectionInfo.InternalConnectionString).To(Equal(connString), "Wrong internal zkConnectionString in status") @@ -348,7 +344,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { "ZK_HOST": expectedZKHost, "ZK_SERVER": "host:7271,host2:7271", "ZK_CHROOT": "/a-ch/root", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "8983", "GC_TUNE": "", } @@ -374,7 +370,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct default values", func() { + FIt("has the correct default values", func(ctx context.Context) { expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { // Solr defaults @@ -434,7 +430,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -443,7 +439,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain, "SOLR_PORT": "2000", "SOLR_NODE_PORT": "2000", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)", @@ -494,7 +490,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr ConfigMap") configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLStringForCloud(solrCloud)}) @@ -533,7 +529,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("ensuring no statefulSet exists when the configMap doesn't exist") expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) @@ -605,7 +601,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: testCustomConfigMap, @@ -692,7 +688,7 @@ var _ = FDescribe("SolrCloud controller - General", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: testCustomConfigMap, diff --git a/controllers/solrcloud_controller_tls_test.go b/controllers/solrcloud_controller_tls_test.go index fe55021a..a585f1d1 100644 --- a/controllers/solrcloud_controller_tls_test.go +++ b/controllers/solrcloud_controller_tls_test.go @@ -36,14 +36,10 @@ import ( var _ = FDescribe("SolrCloud controller - TLS", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - replicas := int32(1) solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ @@ -69,17 +65,17 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) By("defaulting the missing SolrCloud values") - expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { + solrCloud = expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.WithDefaults(logger)).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually") }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -90,7 +86,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{AuthenticationType: solrv1beta1.Basic} // with basic-auth too solrCloud.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false) }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) { g.Expect(found.Spec.SolrAddressability.External.NodePortOverride).To(Equal(443), "Node port override wrong after Spec defaulting") }) @@ -137,7 +133,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{AuthenticationType: solrv1beta1.Basic} solrCloud.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false) }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { Expect(util.GetCustomProbePaths(solrCloud)).To(ConsistOf("/solr/admin/info/health"), "Utility Probe paths command gives wrong result") verifyUserSuppliedTLSConfig(solrCloud.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName) @@ -161,7 +157,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { VerifyClientHostname: true, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("checking that the Mounted TLS Config is correct in the generated StatefulSet") expectStatefulSetMountedTLSDirConfig(ctx, solrCloud) }) @@ -183,7 +179,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { VerifyClientHostname: true, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("checking that the Mounted TLS Config is correct in the generated StatefulSet") expectStatefulSetMountedTLSDirConfig(ctx, solrCloud) @@ -207,7 +203,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { } solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{AuthenticationType: solrv1beta1.Basic} }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("checking that the Mounted TLS Config is correct in the generated StatefulSet") expectStatefulSetMountedTLSDirConfig(ctx, solrCloud) By("checking that the BasicAuth works as expected when using TLS") @@ -231,7 +227,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { } solrCloud.Spec.SolrAddressability.External.HideNodes = true }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("checking that the Mounted TLS Config is correct in the generated StatefulSet") foundStatefulSet := expectStatefulSetMountedTLSDirConfig(ctx, solrCloud) @@ -258,7 +254,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { } solrCloud.Spec.SolrTLS.ClientAuth = solrv1beta1.Need // require client auth too (mTLS between the pods) }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { verifyUserSuppliedTLSConfig(solrCloud.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName) By("checking that the User supplied TLS Config is correct in the generated StatefulSet") verifyReconcileUserSuppliedTLS(ctx, solrCloud, false, false) @@ -277,7 +273,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{AuthenticationType: solrv1beta1.Basic} solrCloud.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false) }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { verifyUserSuppliedTLSConfig(solrCloud.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName) By("checking that the User supplied TLS Config is correct in the generated StatefulSet") verifyReconcileUserSuppliedTLS(ctx, solrCloud, true, false) @@ -294,7 +290,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { solrCloud.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, true) solrCloud.Spec.SolrTLS.ClientAuth = solrv1beta1.Need }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { verifyUserSuppliedTLSConfig(solrCloud.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName) By("checking that the User supplied TLS Config is correct in the generated StatefulSet") verifyReconcileUserSuppliedTLS(ctx, solrCloud, false, true) @@ -328,7 +324,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { RestartOnTLSSecretUpdate: true, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("checking that the User supplied TLS Config is correct in the generated StatefulSet") verifyReconcileUserSuppliedTLS(ctx, solrCloud, false, true) @@ -343,7 +339,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { BeforeEach(func() { solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{AuthenticationType: solrv1beta1.Basic} }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("checking that the BasicAuth works as expected when using TLS") expectStatefulSetBasicAuthConfig(ctx, solrCloud, true) @@ -386,7 +382,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { TLSSecret: tlsSecretName, } }) - FIt("has the correct resources - Explicit Secret", func() { + FIt("has the correct resources - Explicit Secret", func(ctx context.Context) { By("Checking that the Ingress will terminate TLS") expectTerminateIngressTLSConfig(ctx, solrCloud, tlsSecretName, false) @@ -411,7 +407,7 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { UseDefaultTLSSecret: true, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("Checking that the Ingress will terminate TLS") expectTerminateIngressTLSConfig(ctx, solrCloud, "", false) @@ -431,12 +427,12 @@ var _ = FDescribe("SolrCloud controller - TLS", func() { }) // Ensures all the TLS env vars, volume mounts and initContainers are setup for the PodTemplateSpec -func expectTLSConfigOnPodTemplate(tls *solrv1beta1.SolrTLSOptions, podTemplate *corev1.PodTemplateSpec, needsPkcs12InitContainer bool, clientOnly bool, clientTLS *solrv1beta1.SolrTLSOptions) *corev1.Container { - return expectTLSConfigOnPodTemplateWithGomega(Default, tls, podTemplate, needsPkcs12InitContainer, clientOnly, clientTLS) +func expectTLSConfigOnPodTemplate(solrCloud *solrv1beta1.SolrCloud, tls *solrv1beta1.SolrTLSOptions, podTemplate *corev1.PodTemplateSpec, needsPkcs12InitContainer bool, clientOnly bool, clientTLS *solrv1beta1.SolrTLSOptions) *corev1.Container { + return expectTLSConfigOnPodTemplateWithGomega(Default, solrCloud, tls, podTemplate, needsPkcs12InitContainer, clientOnly, clientTLS) } // Ensures all the TLS env vars, volume mounts and initContainers are setup for the PodTemplateSpec -func expectTLSConfigOnPodTemplateWithGomega(g Gomega, tls *solrv1beta1.SolrTLSOptions, podTemplate *corev1.PodTemplateSpec, needsPkcs12InitContainer bool, clientOnly bool, clientTLS *solrv1beta1.SolrTLSOptions) *corev1.Container { +func expectTLSConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1beta1.SolrCloud, tls *solrv1beta1.SolrTLSOptions, podTemplate *corev1.PodTemplateSpec, needsPkcs12InitContainer bool, clientOnly bool, clientTLS *solrv1beta1.SolrTLSOptions) *corev1.Container { g.Expect(podTemplate.Spec.Volumes).To(Not(BeNil()), "Solr Pod Volumes should not be nil when using TLS") if tls.PKCS12Secret != nil { @@ -531,85 +527,126 @@ func expectTLSConfigOnPodTemplateWithGomega(g Gomega, tls *solrv1beta1.SolrTLSOp g.Expect(expInitContainer.Command[2]).To(Equal(expCmd), "Wrong TLS initContainer command") } - if tls.ClientAuth == solrv1beta1.Need { - // verify the probes use a command with SSL opts - tlsProps := "" - if clientTLS != nil { - tlsProps = "-Djavax.net.ssl.trustStore=$SOLR_SSL_CLIENT_TRUST_STORE -Djavax.net.ssl.keyStore=$SOLR_SSL_CLIENT_KEY_STORE" + - " -Djavax.net.ssl.keyStorePassword=$SOLR_SSL_CLIENT_KEY_STORE_PASSWORD -Djavax.net.ssl.trustStorePassword=$SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD" - } else { - tlsProps = "-Djavax.net.ssl.trustStore=$SOLR_SSL_TRUST_STORE -Djavax.net.ssl.keyStore=$SOLR_SSL_KEY_STORE" + - " -Djavax.net.ssl.keyStorePassword=$SOLR_SSL_KEY_STORE_PASSWORD -Djavax.net.ssl.trustStorePassword=$SOLR_SSL_TRUST_STORE_PASSWORD" - } + if tls.MountedTLSDir != nil { + expectMountedTLSDirConfigOnPodTemplateWithGomega(g, podTemplate, solrCloud) + } + + if solrCloud != nil && (solrCloud.Spec.SolrTLS.ClientAuth == solrv1beta1.Need || solrCloud.Spec.SolrTLS.VerifyClientHostname) { g.Expect(mainContainer.LivenessProbe).To(Not(BeNil()), "main container should have a liveness probe defined") - g.Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when auth is enabled") + g.Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when mTLS is enabled") + g.Expect(mainContainer.LivenessProbe.Exec.Command).To(Not(BeNil()), "liveness probe should have an exec when mTLS is enabled") g.Expect(mainContainer.LivenessProbe.Exec.Command).To(HaveLen(3), "liveness probe command has wrong number of args") - g.Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(ContainSubstring(tlsProps), "liveness probe should invoke java with SSL opts") + path := "/solr" + util.DefaultProbePath + if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe != nil { + path = solrCloud.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe.HTTPGet.Path + } + g.Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(ContainSubstring(fmt.Sprintf("solr api -get \"%s://%s:%d%s\"", solrCloud.UrlScheme(false), "${POD_NAME}", solrCloud.Spec.SolrAddressability.PodPort, path)), "liveness probe should invoke solr api -get to contact Solr securely") g.Expect(mainContainer.ReadinessProbe).To(Not(BeNil()), "main container should have a readiness probe defined") - g.Expect(mainContainer.ReadinessProbe.Exec).To(Not(BeNil()), "readiness probe should have an exec when auth is enabled") + g.Expect(mainContainer.ReadinessProbe.Exec).To(Not(BeNil()), "readiness probe should have an exec when mTLS is enabled") g.Expect(mainContainer.ReadinessProbe.Exec.Command).To(HaveLen(3), "readiness probe command has wrong number of args") - g.Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(ContainSubstring(tlsProps), "readiness probe should invoke java with SSL opts") + path = "/solr" + util.DefaultProbePath + if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.ReadinessProbe != nil { + path = solrCloud.Spec.CustomSolrKubeOptions.PodOptions.ReadinessProbe.HTTPGet.Path + } + g.Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(ContainSubstring(fmt.Sprintf("solr api -get \"%s://%s:%d%s\"", solrCloud.UrlScheme(false), "${POD_NAME}", solrCloud.Spec.SolrAddressability.PodPort, path)), "readiness probe should invoke solr api -get to contact Solr securely") + if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.StartupProbe != nil { + g.Expect(mainContainer.StartupProbe).To(Not(BeNil()), "main container should have a startup probe defined") + g.Expect(mainContainer.StartupProbe.Exec).To(Not(BeNil()), "startup probe should have an exec when auth is enabled") + g.Expect(mainContainer.StartupProbe.Exec.Command).To(HaveLen(3), "startup probe command has wrong number of args") + path = "/solr" + util.DefaultProbePath + if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.StartupProbe != nil { + path = solrCloud.Spec.CustomSolrKubeOptions.PodOptions.StartupProbe.HTTPGet.Path + } + g.Expect(mainContainer.StartupProbe.Exec.Command[2]).To(ContainSubstring(fmt.Sprintf("solr api -get \"%s://%s:%d%s\"", solrCloud.UrlScheme(false), "${POD_NAME}", solrCloud.Spec.SolrAddressability.PodPort, path)), "startup probe should invoke solr api -get to contact Solr securely") + } + } else if solrCloud != nil { + g.Expect(mainContainer.LivenessProbe).To(Not(BeNil()), "main container should have a liveness probe defined") + g.Expect(mainContainer.LivenessProbe.HTTPGet).To(Not(BeNil()), "liveness probe should have an http get when client auth is not needed") + g.Expect(mainContainer.LivenessProbe.HTTPGet.HTTPHeaders).To(HaveLen(1), "liveness probe http get should have host header") + g.Expect(mainContainer.LivenessProbe.HTTPGet.HTTPHeaders[0].Name).To(Equal("Host"), "liveness probe http get should have host header") + g.Expect(mainContainer.LivenessProbe.HTTPGet.HTTPHeaders[0].Value).To(Equal(solrCloud.InternalCommonUrl(false)), "liveness probe http get should have a correct host header") + g.Expect(mainContainer.ReadinessProbe).To(Not(BeNil()), "main container should have a readiness probe defined") + g.Expect(mainContainer.ReadinessProbe.HTTPGet).To(Not(BeNil()), "readiness probe should have an http get when client auth is not needed") + g.Expect(mainContainer.ReadinessProbe.HTTPGet.HTTPHeaders).To(HaveLen(1), "readiness probe http get should have host header") + g.Expect(mainContainer.ReadinessProbe.HTTPGet.HTTPHeaders[0].Name).To(Equal("Host"), "readiness probe http get should have host header") + g.Expect(mainContainer.ReadinessProbe.HTTPGet.HTTPHeaders[0].Value).To(Equal(solrCloud.InternalCommonUrl(false)), "readiness probe http get should have a correct host header") + if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.StartupProbe != nil { + g.Expect(mainContainer.StartupProbe).To(Not(BeNil()), "main container should have a startup probe defined") + g.Expect(mainContainer.StartupProbe.HTTPGet).To(Not(BeNil()), "startup probe should have an http get when client auth is not needed") + g.Expect(mainContainer.StartupProbe.HTTPGet.HTTPHeaders).To(HaveLen(1), "startup probe http get should have host header") + g.Expect(mainContainer.StartupProbe.HTTPGet.HTTPHeaders[0].Name).To(Equal("Host"), "startup probe http get should have host header") + g.Expect(mainContainer.StartupProbe.HTTPGet.HTTPHeaders[0].Value).To(Equal(solrCloud.InternalCommonUrl(false)), "startup probe http get should have a correct host header") + } } return &mainContainer // return as a convenience in case tests want to do more checking on the main container } -func expectMountedTLSDirEnvVars(envVars []corev1.EnvVar, solrCloud *solrv1beta1.SolrCloud) { - Expect(envVars).To(Not(BeNil()), "Solr Pod must have env vars when using Mounted TLS Dir") +func expectMountedTLSDirEnvVars(g Gomega, envVars []corev1.EnvVar, solrCloud *solrv1beta1.SolrCloud) { + g.Expect(envVars).To(Not(BeNil()), "Solr Pod must have env vars when using Mounted TLS Dir") envVars = filterVarsByName(envVars, func(n string) bool { return strings.HasPrefix(n, "SOLR_SSL_") }) - expectedTLSVarsCount := 6 + expectedTLSVarsCount := 8 if solrCloud.Spec.SolrClientTLS != nil { - expectedTLSVarsCount += 2 - Expect(len(envVars)).To(Equal(8), "expected SOLR_SSL and SOLR_SSL_CLIENT related env vars not found") + expectedTLSVarsCount += 4 if solrCloud.Spec.SolrClientTLS.MountedTLSDir != nil { - if solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePassword != "" { + expectedTLSVarsCount -= 2 + if solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile == "" && solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePassword != "" { expectedTLSVarsCount += 1 } - if solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePassword != "" { + if solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePasswordFile == "" && solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePassword != "" { expectedTLSVarsCount += 1 } } } + + if !solrCloud.Spec.SolrTLS.VerifyClientHostname { + expectedTLSVarsCount += 1 + } if solrCloud.Spec.SolrTLS.MountedTLSDir != nil { - if solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePassword != "" { + expectedTLSVarsCount -= 2 + if solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile == "" && solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePassword != "" { expectedTLSVarsCount += 1 } - if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePassword != "" { + if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePasswordFile == "" && solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePassword != "" { expectedTLSVarsCount += 1 } } - Expect(len(envVars)).To(Equal(expectedTLSVarsCount), "expected SOLR_SSL related env vars not found") + g.Expect(envVars).To(HaveLen(expectedTLSVarsCount), "expected SOLR_SSL (and maybe SOLR_SSL_CLIENT) related env vars not found") expectedKeystorePath := solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrTLS.MountedTLSDir.KeystoreFile expectedTruststorePath := solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrTLS.MountedTLSDir.TruststoreFile for _, envVar := range envVars { if envVar.Name == "SOLR_SSL_ENABLED" { - Expect(envVar.Value).To(Equal("true"), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal("true"), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_KEY_STORE" { - Expect(envVar.Value).To(Equal(expectedKeystorePath), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal(expectedKeystorePath), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_TRUST_STORE" { - Expect(envVar.Value).To(Equal(expectedTruststorePath), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal(expectedTruststorePath), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_WANT_CLIENT_AUTH" { - Expect(envVar.Value).To(Equal("false"), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal("false"), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_NEED_CLIENT_AUTH" { - Expect(envVar.Value).To(Equal("true"), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal("true"), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_CHECK_PEER_NAME" { - Expect(envVar.Value).To(Equal("true"), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal("true"), "Wrong envVar value for %s", envVar.Name) + } + + if envVar.Name == "SOLR_SSL_CLIENT_HOSTNAME_VERIFICATION" { + g.Expect(envVar.Value).To(Equal("false"), "Wrong envVar value for %s", envVar.Name) } } @@ -617,20 +654,20 @@ func expectMountedTLSDirEnvVars(envVars []corev1.EnvVar, solrCloud *solrv1beta1. for _, envVar := range envVars { if envVar.Name == "SOLR_SSL_CLIENT_KEY_STORE" { expectedKeystorePath = solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystoreFile - Expect(envVar.Value).To(Equal(expectedKeystorePath), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal(expectedKeystorePath), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_CLIENT_TRUST_STORE" { expectedTruststorePath = solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststoreFile - Expect(envVar.Value).To(Equal(expectedTruststorePath), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal(expectedTruststorePath), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_CLIENT_KEY_STORE_PASSWORD" { - Expect(envVar.Value).To(Equal(solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePassword), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal(solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePassword), "Wrong envVar value for %s", envVar.Name) } if envVar.Name == "SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD" { - Expect(envVar.Value).To(Equal(solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePassword), "Wrong envVar value for %s", envVar.Name) + g.Expect(envVar.Value).To(Equal(solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePassword), "Wrong envVar value for %s", envVar.Name) } } } @@ -762,7 +799,7 @@ func createMockTLSSecret(ctx context.Context, parentObject client.Object, secret func expectStatefulSetMountedTLSDirConfig(ctx context.Context, solrCloud *solrv1beta1.SolrCloud) *appsv1.StatefulSet { statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) podTemplate := &statefulSet.Spec.Template - expectMountedTLSDirConfigOnPodTemplate(podTemplate, solrCloud) + expectTLSConfigOnPodTemplateWithGomega(Default, solrCloud, solrCloud.Spec.SolrTLS, podTemplate, false, false, solrCloud.Spec.SolrClientTLS) // Check HTTPS cluster prop setup container Expect(podTemplate.Spec.InitContainers).To(Not(BeEmpty()), "Init containers cannot be empty when using Mounted TLS") @@ -770,90 +807,95 @@ func expectStatefulSetMountedTLSDirConfig(ctx context.Context, solrCloud *solrv1 // should have a mount for the initdb on main container expectInitdbVolumeMount(podTemplate) + return statefulSet +} - // verify initContainer to create initdb solrCloudript to export the keystore & truststore passwords before launching the main container - name := "export-tls-password" - expInitContainer := expectInitContainer(podTemplate, name, "initdb", util.InitdbPath) - Expect(len(expInitContainer.Command)).To(Equal(3), "Wrong command length for %s init container command", name) - if solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile != "" && solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePassword == "" { - Expect(expInitContainer.Command[2]).To(ContainSubstring("SOLR_SSL_KEY_STORE_PASSWORD"), "Wrong shell command for init container: %s", name) - } else { - Expect(expInitContainer.Command[2]).To(Not(ContainSubstring("SOLR_SSL_KEY_STORE_PASSWORD")), "Wrong shell command for init container: %s", name) +func expectInitDBGeneratingInitContainer(g Gomega, podTemplate *corev1.PodTemplateSpec, solrCloud *solrv1beta1.SolrCloud) { + // verify the probes use a command with SSL opts + expectedServerKeystorePasswordSource := "" + expectedServerTruststorePasswordSource := "" + expectedClientKeystorePasswordSource := "" + expectedClientTruststorePasswordSource := "" + needsInitContainer := false + // if there's a client cert, then the probe should use that, else uses the server cert + if solrCloud.Spec.SolrTLS != nil && solrCloud.Spec.SolrTLS.MountedTLSDir != nil { + if solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile != "" { + expectedServerKeystorePasswordSource = solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile + needsInitContainer = true + } else if solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePassword == "" { + expectedServerKeystorePasswordSource = solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/keystore-password" + needsInitContainer = true + } + if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePasswordFile != "" { + expectedServerTruststorePasswordSource = solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePasswordFile + needsInitContainer = true + } else if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePassword == "" { + expectedServerTruststorePasswordSource = "${SOLR_SSL_KEY_STORE_PASSWORD}" + needsInitContainer = true + } } - if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePasswordFile != "" || (solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePassword == "" && solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile != "") { - Expect(expInitContainer.Command[2]).To(ContainSubstring("SOLR_SSL_TRUST_STORE_PASSWORD"), "Wrong shell command for init container: %s", name) - } else { - Expect(expInitContainer.Command[2]).To(Not(ContainSubstring("SOLR_SSL_TRUST_STORE_PASSWORD")), "Wrong shell command for init container: %s", name) + if solrCloud.Spec.SolrClientTLS != nil && solrCloud.Spec.SolrClientTLS.MountedTLSDir != nil { + if solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile != "" { + expectedClientKeystorePasswordSource = solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile + needsInitContainer = true + } else if solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePassword == "" { + expectedClientKeystorePasswordSource = solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/keystore-password" + needsInitContainer = true + } + if solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePasswordFile != "" { + expectedClientTruststorePasswordSource = solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePasswordFile + needsInitContainer = true + } else if solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePassword == "" { + expectedClientTruststorePasswordSource = "${SOLR_SSL_CLIENT_KEY_STORE_PASSWORD}" + needsInitContainer = true + } } - if solrCloud.Spec.SolrClientTLS != nil && solrCloud.Spec.SolrClientTLS.MountedTLSDir != nil { - if solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile != "" && solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePassword == "" { - Expect(expInitContainer.Command[2]).To(ContainSubstring("SOLR_SSL_CLIENT_KEY_STORE_PASSWORD"), "Wrong shell command for init container: %s", name) - } else { - Expect(expInitContainer.Command[2]).To(Not(ContainSubstring("SOLR_SSL_CLIENT_KEY_STORE_PASSWORD")), "Wrong shell command for init container: %s", name) + var initDBInitContainer *corev1.Container + for _, container := range podTemplate.Spec.InitContainers { + if container.Name == util.InitdbInitContainer { + initDBInitContainer = &container } - if solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePasswordFile != "" || (solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePassword == "" && solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile != "") { - Expect(expInitContainer.Command[2]).To(ContainSubstring("SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD"), "Wrong shell command for init container: %s", name) + } + if needsInitContainer { + g.Expect(initDBInitContainer).ToNot(BeNil(), "Did not find an initDB generating init container when TLS passwords/Basic Auth are stored in files") + + expectExportedVariable(g, initDBInitContainer.Command[2], "SOLR_SSL_KEY_STORE_PASSWORD", expectedServerKeystorePasswordSource) + expectExportedVariable(g, initDBInitContainer.Command[2], "SOLR_SSL_TRUST_STORE_PASSWORD", expectedServerTruststorePasswordSource) + expectExportedVariable(g, initDBInitContainer.Command[2], "SOLR_SSL_CLIENT_KEY_STORE_PASSWORD", expectedClientKeystorePasswordSource) + expectExportedVariable(g, initDBInitContainer.Command[2], "SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD", expectedClientTruststorePasswordSource) + } else { + g.Expect(initDBInitContainer).To(BeNil(), "Found an initDB generating init container when TLS passwords/Basic Auth are NOT stored in files") + } +} + +func expectExportedVariable(g Gomega, command string, varName string, valueSource string) { + if valueSource != "" { + if strings.HasPrefix(valueSource, "${") { + // This is a passed-through variable + g.ExpectWithOffset(1, command).To(ContainSubstring(varName+"=\""+valueSource+"\""), "Exported variable not found in initDB script") } else { - Expect(expInitContainer.Command[2]).To(Not(ContainSubstring("SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD")), "Wrong shell command for init container: %s", name) + // This is a file path + g.ExpectWithOffset(1, command).To(ContainSubstring(varName+"=\"$(cat \""+valueSource+"\")\""), "Exported variable not found in initDB script") } } else { - Expect(expInitContainer.Command[2]).To(Not(ContainSubstring("SOLR_SSL_CLIENT_KEY_STORE_PASSWORD")), "Wrong shell command for init container: %s", name) - Expect(expInitContainer.Command[2]).To(Not(ContainSubstring("SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD")), "Wrong shell command for init container: %s", name) + g.ExpectWithOffset(1, command).ToNot(ContainSubstring(varName), "Exported variable found in initDB script that should not be there") } - return statefulSet } func expectMountedTLSDirConfigOnPodTemplate(podTemplate *corev1.PodTemplateSpec, solrCloud *solrv1beta1.SolrCloud) { - Expect(podTemplate.Spec.Containers).To(Not(BeEmpty()), "Solr Pod must have containers") - mainContainer := podTemplate.Spec.Containers[0] - Expect(mainContainer).To(Not(BeNil()), "Didn't find the main solrcloud-node container in the sts!") - Expect(mainContainer.Env).To(Not(BeEmpty()), "No Env vars for main solrcloud-node container in the sts!") - expectMountedTLSDirEnvVars(mainContainer.Env, solrCloud) - - // verify the probes use a command with SSL opts - tlsJavaToolOpts, tlsJavaSysProps := "", "" - // if there's a client cert, then the probe should use that, else uses the server cert - if solrCloud.Spec.SolrClientTLS != nil && solrCloud.Spec.SolrClientTLS.MountedTLSDir != nil { - expectedKeystorePasswordFile := solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile - expectedTruststorePasswordFile := solrCloud.Spec.SolrClientTLS.MountedTLSDir.Path + "/" - if solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePasswordFile != "" { - expectedTruststorePasswordFile += solrCloud.Spec.SolrClientTLS.MountedTLSDir.TruststorePasswordFile - } else { - expectedTruststorePasswordFile += solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystorePasswordFile - } + expectMountedTLSDirConfigOnPodTemplateWithGomega(Default, podTemplate, solrCloud) +} - tlsJavaToolOpts = "-Djavax.net.ssl.keyStorePassword=$(cat " + expectedKeystorePasswordFile + ") " + - "-Djavax.net.ssl.trustStorePassword=$(cat " + expectedTruststorePasswordFile + ")" - tlsJavaSysProps = "-Djavax.net.ssl.trustStore=$SOLR_SSL_CLIENT_TRUST_STORE -Djavax.net.ssl.keyStore=$SOLR_SSL_CLIENT_KEY_STORE" - } else { - expectedKeystorePassword := "${SOLR_SSL_KEY_STORE_PASSWORD}" - if solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile != "" { - expectedKeystorePassword = "$(cat " + solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrTLS.MountedTLSDir.KeystorePasswordFile + ")" - } - expectedTruststorePassword := expectedKeystorePassword - if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePasswordFile != "" { - expectedTruststorePassword = "$(cat " + solrCloud.Spec.SolrTLS.MountedTLSDir.Path + "/" + solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePasswordFile + ")" - } else if solrCloud.Spec.SolrTLS.MountedTLSDir.TruststorePassword != "" { - expectedTruststorePassword = "${SOLR_SSL_TRUST_STORE_PASSWORD}" - } +func expectMountedTLSDirConfigOnPodTemplateWithGomega(g Gomega, podTemplate *corev1.PodTemplateSpec, solrCloud *solrv1beta1.SolrCloud) { + g.Expect(podTemplate.Spec.Containers).To(Not(BeEmpty()), "Solr Pod must have containers") + mainContainer := podTemplate.Spec.Containers[0] + g.Expect(mainContainer).To(Not(BeNil()), "Didn't find the main solrcloud-node container in the sts!") + g.Expect(mainContainer.Env).To(Not(BeEmpty()), "No Env vars for main solrcloud-node container in the sts!") - tlsJavaToolOpts = "-Djavax.net.ssl.keyStorePassword=" + expectedKeystorePassword + " " + - "-Djavax.net.ssl.trustStorePassword=" + expectedTruststorePassword + "" - tlsJavaSysProps = "-Djavax.net.ssl.trustStore=$SOLR_SSL_TRUST_STORE -Djavax.net.ssl.keyStore=$SOLR_SSL_KEY_STORE" - } + expectMountedTLSDirEnvVars(g, mainContainer.Env, solrCloud) - Expect(mainContainer.LivenessProbe).To(Not(BeNil()), "main container should have a liveness probe defined") - Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when mTLS is enabled") - Expect(mainContainer.LivenessProbe.Exec.Command).To(Not(BeNil()), "liveness probe should have an exec when mTLS is enabled") - Expect(mainContainer.LivenessProbe.Exec.Command).To(Not(BeEmpty()), "liveness probe command cannot be empty") - Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(ContainSubstring(tlsJavaToolOpts), "liveness probe should invoke java with SSL opts") - Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(ContainSubstring(tlsJavaSysProps), "liveness probe should invoke java with SSL opts") - Expect(mainContainer.ReadinessProbe).To(Not(BeNil()), "main container should have a readiness probe defined") - Expect(mainContainer.ReadinessProbe.Exec).To(Not(BeNil()), "readiness probe should have an exec when mTLS is enabled") - Expect(mainContainer.ReadinessProbe.Exec.Command).To(Not(BeEmpty()), "readiness probe command cannot be empty") - Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(ContainSubstring(tlsJavaToolOpts), "readiness probe should invoke java with SSL opts") - Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(ContainSubstring(tlsJavaSysProps), "readiness probe should invoke java with SSL opts") + expectInitDBGeneratingInitContainer(g, podTemplate, solrCloud) } // ensures the TLS settings are applied correctly to the STS @@ -863,7 +905,7 @@ func expectStatefulSetTLSConfig(solrCloud *solrv1beta1.SolrCloud, statefulSet *a func expectStatefulSetTLSConfigWithGomega(g Gomega, solrCloud *solrv1beta1.SolrCloud, statefulSet *appsv1.StatefulSet, needsPkcs12InitContainer bool) { podTemplate := &statefulSet.Spec.Template - expectTLSConfigOnPodTemplateWithGomega(g, solrCloud.Spec.SolrTLS, podTemplate, needsPkcs12InitContainer, false, solrCloud.Spec.SolrClientTLS) + expectTLSConfigOnPodTemplateWithGomega(g, solrCloud, solrCloud.Spec.SolrTLS, podTemplate, needsPkcs12InitContainer, false, solrCloud.Spec.SolrClientTLS) // Check HTTPS cluster prop setup container g.Expect(podTemplate.Spec.InitContainers).To(Not(BeEmpty()), "Solr Pods with TLS require init containers") diff --git a/controllers/solrcloud_controller_zk_test.go b/controllers/solrcloud_controller_zk_test.go index dcd6a75c..8fb8a745 100644 --- a/controllers/solrcloud_controller_zk_test.go +++ b/controllers/solrcloud_controller_zk_test.go @@ -35,14 +35,10 @@ import ( var _ = FDescribe("SolrCloud controller - Zookeeper", func() { var ( - ctx context.Context - solrCloud *solrv1beta1.SolrCloud ) - BeforeEach(func() { - ctx = context.Background() - + BeforeEach(func(ctx context.Context) { solrCloud = &solrv1beta1.SolrCloud{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -54,7 +50,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { cleanupTest(ctx, solrCloud) }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -64,7 +60,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrCloud) }) @@ -89,7 +85,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { SolrOpts: "-Dextra -Dopts", } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the ZK information in the SolrCloud status") expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) { g.Expect(found.ZookeeperConnectionInfo.InternalConnectionString).To(Equal("host:7271"), "Wrong internal zkConnectionString in status") @@ -109,7 +105,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts", @@ -152,7 +148,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { SolrOpts: "-Dextra -Dopts", } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the ZK information in the SolrCloud status") expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) { g.Expect(found.ZookeeperConnectionInfo.InternalConnectionString).To(Equal(connectionString), "Wrong internal zkConnectionString in status") @@ -174,7 +170,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { // Env Variable Tests expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/a-ch/root", - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts", @@ -242,7 +238,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectedZkConnStr := "foo-solrcloud-zookeeper-0.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181,foo-solrcloud-zookeeper-1.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181,foo-solrcloud-zookeeper-2.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181,foo-solrcloud-zookeeper-3.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181" By("testing the ZK information in the SolrCloud status") @@ -261,7 +257,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { expectedZKHost := expectedZkConnStr + "/a-ch/root" expectedEnvVars := map[string]string{ "ZK_HOST": expectedZKHost, - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "ZK_SERVER": expectedZkConnStr, "ZK_CHROOT": "/a-ch/root", "SOLR_PORT": "8983", @@ -350,7 +346,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectedZkConnStr := "foo-solrcloud-zookeeper-0.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181,foo-solrcloud-zookeeper-1.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181,foo-solrcloud-zookeeper-2.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181,foo-solrcloud-zookeeper-3.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181" By("testing the ZK information in the SolrCloud status") @@ -369,7 +365,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { expectedZKHost := expectedZkConnStr + "/" expectedEnvVars := map[string]string{ "ZK_HOST": expectedZKHost, - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "ZK_SERVER": expectedZkConnStr, "SOLR_PORT": "8983", "GC_TUNE": "", @@ -429,7 +425,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { SolrOpts: "-Dextra -Dopts", } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectedZkConnStr := "foo-solrcloud-zookeeper-0.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181" By("testing the ZK information in the SolrCloud status") @@ -454,7 +450,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { expectedZKHost := expectedZkConnStr + "/a-ch/root" expectedEnvVars := map[string]string{ "ZK_HOST": expectedZKHost, - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "ZK_CHROOT": "/a-ch/root", @@ -503,7 +499,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { SolrOpts: "-Dextra -Dopts", } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { expectedZkConnStr := "foo-solrcloud-zookeeper-0.foo-solrcloud-zookeeper-headless.default.svc.cluster.local:2181" By("testing the ZK information in the SolrCloud status") @@ -530,7 +526,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { expectedZKHost := expectedZkConnStr + "/" expectedEnvVars := map[string]string{ "ZK_HOST": expectedZKHost, - "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, + "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace, "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts", @@ -564,13 +560,13 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the Solr StatefulSet") statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName()) Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.") expectedEnvVars := map[string]string{ "ZK_HOST": "host:7271/test", - "SOLR_HOST": "$(POD_HOSTNAME).foo-solrcloud-headless.default", + "SOLR_HOST": "$(POD_NAME).foo-solrcloud-headless.default", "SOLR_PORT": "8983", "SOLR_NODE_PORT": "8983", "SOLR_ZK_OPTS": testSolrZKOpts, diff --git a/controllers/solrprometheusexporter_controller_test.go b/controllers/solrprometheusexporter_controller_test.go index 518b3317..5c259172 100644 --- a/controllers/solrprometheusexporter_controller_test.go +++ b/controllers/solrprometheusexporter_controller_test.go @@ -49,15 +49,11 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { SetDefaultEventuallyPollingInterval(interval) var ( - ctx context.Context - solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter solrRef *solrv1beta1.SolrCloud ) BeforeEach(func() { - ctx = context.Background() - solrPrometheusExporter = &solrv1beta1.SolrPrometheusExporter{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -67,7 +63,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrPrometheusExporter)).To(Succeed()) @@ -77,7 +73,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrPrometheusExporter) }) @@ -100,7 +96,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { ScrapeInterval: testScrapeInterval, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") deployment := expectDeployment(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName()) @@ -180,7 +176,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { Config: testExporterConfig, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter ConfigMap") configMap := expectConfigMap(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsConfigMapName(), map[string]string{util.PrometheusExporterConfigMapKey: testExporterConfig}) Expect(configMap.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrPrometheusExporter.SharedLabelsWith(solrPrometheusExporter.Labels), testConfigMapLabels)), "Incorrect configMap labels") @@ -219,7 +215,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { Expect(deployment.Spec.Template.Spec.NodeSelector).To(Equal(testNodeSelectors), "Incorrect pod node selectors") // Other Pod Options - Expect(deployment.Spec.Template.Spec.Containers[0].Env).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions") + Expect(deployment.Spec.Template.Spec.Containers[0].Env).To(Equal(addBuiltInEnvVars(extraVars)), "Extra Env Vars are not the same as the ones provided in podOptions") Expect(*deployment.Spec.Template.Spec.SecurityContext).To(Equal(testPodSecurityContext), "PodSecurityContext is not the same as the one provided in podOptions") Expect(deployment.Spec.Template.Spec.Affinity).To(Equal(testAffinity), "Affinity is not the same as the one provided in podOptions") Expect(deployment.Spec.Template.Spec.Containers[0].Resources.Limits).To(Equal(testResources.Limits), "Resources.Limits is not the same as the one provided in podOptions") @@ -284,7 +280,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { solrName := "test-solr" testZkCnxString := "host-from-solr:2181" testZKChroot := "/this/path" - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{ SolrReference: solrv1beta1.SolrReference{ Cloud: &solrv1beta1.SolrCloudReference{ @@ -329,7 +325,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { } Expect(k8sClient.Create(ctx, solrRef)).To(Succeed(), "Creating test SolrCloud for Prometheus Exporter to connect to") }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) { expectedArgs := []string{ @@ -403,19 +399,19 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { for _, extraVar := range extraVars { expectedEnvVars[extraVar.Name] = extraVar.Value } - g.Expect(foundEnv[0:5]).To(Equal(zkAclEnvVars), "ZK ACL Env Vars are not correct") - g.Expect(foundEnv[5:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions") + g.Expect(foundEnv[0:7]).To(Equal(addBuiltInEnvVars(zkAclEnvVars)), "ZK ACL Env Vars are not correct") + g.Expect(foundEnv[7:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions") testPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnv) }) By("Changing the ZKConnection String of the SolrCloud") newZkCnxString := "new-host:2181,another:2181" - Eventually(func(g Gomega) { + Eventually(func(g Gomega, ctx context.Context) { foundSolrRef := &solrv1beta1.SolrCloud{} g.Expect(k8sClient.Get(ctx, resourceKey(solrRef, solrRef.Name), foundSolrRef)).To(Succeed(), "Failed getting test SolrCloud reffered to by the SolrPrometheusExporter") foundSolrRef.Spec.ZookeeperRef.ConnectionInfo.InternalConnectionString = newZkCnxString g.Expect(k8sClient.Update(ctx, foundSolrRef)).To(Succeed(), "Failed updating the ZK info for the test SolrCloud") - }).Should(Succeed(), "Could not update the ZKConnectionString for the SolrCloud") + }).WithContext(ctx).Should(Succeed(), "Could not update the ZKConnectionString for the SolrCloud") By("making sure the PrometheusExporter Deployment updates to use the new ZK Connection String") expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) { @@ -434,7 +430,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { testZkCnxString := "host-from-solr:2181" testZKChroot := "/this/path" withUserProvidedConfigMapName := "custom-exporter-config" - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{ SolrReference: solrv1beta1.SolrReference{ Cloud: &solrv1beta1.SolrCloudReference{ @@ -476,7 +472,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() { Expect(k8sClient.Create(ctx, solrRef)).To(Succeed(), "Creating test SolrCloud for Prometheus Exporter to connect to") }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment is not created without an existing configMap") expectNoDeployment(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName()) @@ -532,3 +528,26 @@ func updateUserProvidedConfigMap(ctx context.Context, parentResource client.Obje foundConfigMap.Data = dataMap Expect(k8sClient.Update(ctx, foundConfigMap)).NotTo(HaveOccurred(), "Issue updating the user provided configMap") } + +func addBuiltInEnvVars(customEnvVars []corev1.EnvVar) []corev1.EnvVar { + return append([]corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + APIVersion: "v1", + }, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + APIVersion: "v1", + }, + }, + }, + }, customEnvVars...) +} diff --git a/controllers/solrprometheusexporter_controller_tls_test.go b/controllers/solrprometheusexporter_controller_tls_test.go index 07928b00..d5c4db6d 100644 --- a/controllers/solrprometheusexporter_controller_tls_test.go +++ b/controllers/solrprometheusexporter_controller_tls_test.go @@ -48,12 +48,10 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { SetDefaultEventuallyPollingInterval(interval) var ( - ctx context.Context - solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter ) - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { ctx = context.Background() solrPrometheusExporter = &solrv1beta1.SolrPrometheusExporter{ @@ -65,7 +63,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { } }) - JustBeforeEach(func() { + JustBeforeEach(func(ctx context.Context) { By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrPrometheusExporter)).To(Succeed()) @@ -75,7 +73,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }) }) - AfterEach(func() { + AfterEach(func(ctx context.Context) { cleanupTest(ctx, solrPrometheusExporter) }) @@ -94,7 +92,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false) }) @@ -115,7 +113,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, true) }) @@ -136,7 +134,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false) }) @@ -145,7 +143,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { FContext("TLS Secret - With BasicAuth", func() { tlsSecretName := "tls-cert-secret-update" basicAuthSecretName := tlsSecretName + "-basic-auth" - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{ SolrReference: solrv1beta1.SolrReference{ Cloud: &solrv1beta1.SolrCloudReference{ @@ -162,7 +160,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { By("Creating secret to use with BasicAuth") Expect(k8sClient.Create(ctx, createBasicAuthSecret(basicAuthSecretName, solrv1beta1.DefaultBasicAuthUsername, solrPrometheusExporter.Namespace))).To(Succeed(), "Could not create basic auth secret") }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false) }) @@ -171,7 +169,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { FContext("TLS Secret - With BasicAuth - Restart on Secret Update", func() { tlsSecretName := "tls-cert-secret-update" basicAuthSecretName := tlsSecretName + "-basic-auth" - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{ SolrReference: solrv1beta1.SolrReference{ Cloud: &solrv1beta1.SolrCloudReference{ @@ -188,7 +186,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { By("Creating secret to use with BasicAuth") Expect(k8sClient.Create(ctx, createBasicAuthSecret(basicAuthSecretName, solrv1beta1.DefaultBasicAuthUsername, solrPrometheusExporter.Namespace))).To(Succeed(), "Could not create basic auth secret") }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false) @@ -222,7 +220,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTruststoreOnly(ctx, solrPrometheusExporter, tlsSecretName) }) @@ -253,7 +251,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") testReconcileWithTruststoreOnly(ctx, solrPrometheusExporter, tlsSecretName) }) @@ -288,7 +286,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() { }, } }) - FIt("has the correct resources", func() { + FIt("has the correct resources", func(ctx context.Context) { By("testing the SolrPrometheusExporter Deployment") deployment := expectDeployment(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName()) @@ -348,7 +346,7 @@ func testReconcileWithTLS(ctx context.Context, solrPrometheusExporter *solrv1bet mockSecret := createMockTLSSecret(ctx, solrPrometheusExporter, tlsSecretName, tlsKey, keystorePassKey, "") deployment := expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) { - expectTLSConfigOnPodTemplateWithGomega(g, solrPrometheusExporter.Spec.SolrReference.SolrTLS, &found.Spec.Template, needsPkcs12InitContainer, true, nil) + expectTLSConfigOnPodTemplateWithGomega(g, nil, solrPrometheusExporter.Spec.SolrReference.SolrTLS, &found.Spec.Template, needsPkcs12InitContainer, true, nil) }) mainContainer := &deployment.Spec.Template.Spec.Containers[0] @@ -421,7 +419,7 @@ func testReconcileWithTruststoreOnly(ctx context.Context, solrPrometheusExporter mockSecret := createMockTLSSecret(ctx, solrPrometheusExporter, tlsSecretName, tlsKey, keystorePassKey, "") deployment := expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) { - expectTLSConfigOnPodTemplateWithGomega(g, solrPrometheusExporter.Spec.SolrReference.SolrTLS, &found.Spec.Template, false, true, nil) + expectTLSConfigOnPodTemplateWithGomega(g, nil, solrPrometheusExporter.Spec.SolrReference.SolrTLS, &found.Spec.Template, false, true, nil) }) mainContainer := &deployment.Spec.Template.Spec.Containers[0] diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 804eb61d..4d49ef58 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -57,7 +57,7 @@ func TestAPIs(t *testing.T) { RunSpecs(t, "Controller Suite") } -var _ = BeforeSuite(func() { +var _ = BeforeSuite(func(ctx context.Context) { // Define testing timeouts/durations and intervals. const ( timeout = time.Second * 5 @@ -131,6 +131,5 @@ var _ = BeforeSuite(func() { var _ = AfterSuite(func() { cancel() By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).ToNot(HaveOccurred()) + Expect(testEnv.Stop()).To(Succeed()) }) diff --git a/controllers/util/prometheus_exporter_util.go b/controllers/util/prometheus_exporter_util.go index de702839..81599b6c 100644 --- a/controllers/util/prometheus_exporter_util.go +++ b/controllers/util/prometheus_exporter_util.go @@ -78,7 +78,26 @@ func GenerateSolrPrometheusExporterDeployment(solrPrometheusExporter *solr.SolrP imagePullSecrets = customPodOptions.ImagePullSecrets } - var envVars []corev1.EnvVar + envVars := []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + APIVersion: "v1", + }, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + APIVersion: "v1", + }, + }, + }, + } var allJavaOpts []string var solrVolumes []corev1.Volume diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go index d4f7e719..3f94de6f 100644 --- a/controllers/util/solr_security_util.go +++ b/controllers/util/solr_security_util.go @@ -205,6 +205,35 @@ func enableSecureProbesOnSolrCloudStatefulSet(solrCloud *solr.SolrCloud, statefu } } +func setHostHeaderForProbesOnSolrCloudStatefulSet(solrCloud *solr.SolrCloud, stateful *appsv1.StatefulSet) { + mainContainer := &stateful.Spec.Template.Spec.Containers[0] + + expectedHost := solrCloud.InternalCommonUrl(false) + // update the probes if they are using HTTPGet to use send a HOST header matching the host Solr is listening on + if mainContainer.LivenessProbe.HTTPGet != nil { + addHostHeaderToProbe(mainContainer.LivenessProbe.HTTPGet, expectedHost) + } + if mainContainer.ReadinessProbe.HTTPGet != nil { + addHostHeaderToProbe(mainContainer.ReadinessProbe.HTTPGet, expectedHost) + } + if mainContainer.StartupProbe != nil && mainContainer.StartupProbe.HTTPGet != nil { + addHostHeaderToProbe(mainContainer.StartupProbe.HTTPGet, expectedHost) + } +} + +func addHostHeaderToProbe(httpGet *corev1.HTTPGetAction, host string) { + for _, header := range httpGet.HTTPHeaders { + if header.Name == "Host" { + // Do not add a Host header if it already exists + return + } + } + httpGet.HTTPHeaders = append(httpGet.HTTPHeaders, corev1.HTTPHeader{ + Name: "Host", + Value: host, + }) +} + func cmdToPutSecurityJsonInZk() string { scriptsDir := "/opt/solr/server/scripts/cloud-scripts" cmd := " ZK_SECURITY_JSON=$(%s/zkcli.sh -zkhost ${ZK_HOST} -cmd get /security.json); " @@ -450,28 +479,27 @@ func BasicAuthEnvVars(secretName string) []corev1.EnvVar { 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 := "" + javaToolOptions := make([]string, 0) if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.ProbesRequireAuth && solrCloud.Spec.SolrSecurity.AuthenticationType == solr.Basic { 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 " + javaToolOptions = append(javaToolOptions, + fmt.Sprintf("-Dbasicauth=$(cat %s):$(cat %s)", usernameFile, passwordFile), + "-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) + + // Future work - SOLR_TOOL_OPTIONS is only in 9.4.0, use JAVA_TOOL_OPTIONS until that is the minimum supported version + var javaToolOptionsStr string + if len(javaToolOptions) > 0 { + javaToolOptionsStr = fmt.Sprintf("JAVA_TOOL_OPTIONS=%q ", strings.Join(javaToolOptions, " ")) + } else { + javaToolOptionsStr = "" + } + + probeCommand := fmt.Sprintf("%ssolr api -get \"%s://%s:%d%s\"", javaToolOptionsStr, solrCloud.UrlScheme(false), "${POD_NAME}", probe.HTTPGet.Port.IntVal, probe.HTTPGet.Path) probeCommand = regexp.MustCompile(`\s+`).ReplaceAllString(strings.TrimSpace(probeCommand), " ") // use an Exec instead of an HTTP GET diff --git a/controllers/util/solr_tls_util.go b/controllers/util/solr_tls_util.go index 7316cb59..676311b1 100644 --- a/controllers/util/solr_tls_util.go +++ b/controllers/util/solr_tls_util.go @@ -39,6 +39,7 @@ const ( TLSCertKey = "tls.crt" DefaultTrustStorePath = "/var/solr/tls-truststore" DefaultClientTrustStorePath = "/var/solr/client-tls-truststore" + InitdbInitContainer = "generate-init-db" InitdbPath = "/docker-entrypoint-initdb.d" DefaultPkcs12KeystoreFile = "keystore.p12" DefaultPkcs12TruststoreFile = "truststore.p12" @@ -168,7 +169,7 @@ func (tls *TLSCerts) enableTLSOnExporterDeployment(deployment *appsv1.Deployment if clientCert.Options.PKCS12Secret != nil || clientCert.Options.TrustStoreSecret != nil { // Cert comes from a secret, so setup the pod template to mount the secret clientCert.mountTLSSecretOnPodTemplate(&deployment.Spec.Template) - } else if clientCert.Options.MountedTLSDir != nil { + } else if clientCert.Options.MountedTLSDir != nil && clientCert.hasPasswordsInFiles() { // volumes and mounts for TLS when using the mounted dir option clientCert.mountTLSWrapperScriptAndInitContainer(deployment, tls.InitContainerImage) } @@ -557,19 +558,27 @@ func (tls *TLSConfig) mountTLSWrapperScriptAndInitContainer(deployment *appsv1.D kspJavaSysProp := "" catKsp := "" if opts.MountedTLSDir.KeystoreFile != "" { - catKsp = fmt.Sprintf("ksp=\\$(cat %s)", mountedTLSKeystorePasswordPath(opts.MountedTLSDir)) - kspJavaSysProp = " -Djavax.net.ssl.keyStorePassword=\\${ksp}" + if opts.MountedTLSDir.KeystorePasswordFile != "" || opts.MountedTLSDir.KeystorePassword == "" { + catKsp = fmt.Sprintf("SOLR_SSL_CLIENT_KEY_STORE_PASSWORD=\\$(cat %s)", mountedTLSKeystorePasswordPath(opts.MountedTLSDir)) + kspJavaSysProp = " -Djavax.net.ssl.keyStorePassword=\\${SOLR_SSL_CLIENT_KEY_STORE_PASSWORD}" + } } - catTsp := fmt.Sprintf("tsp=\\$(cat %s)", mountedTLSTruststorePasswordPath(opts.MountedTLSDir)) - tspJavaSysProp := " -Djavax.net.ssl.trustStorePassword=\\${tsp}" + tspJavaSysProp := "" + catTsp := "" + if opts.MountedTLSDir.TruststorePasswordFile != "" || opts.MountedTLSDir.TruststorePassword == "" { + catTsp = fmt.Sprintf("SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD=\\$(cat %s)", mountedTLSTruststorePasswordPath(opts.MountedTLSDir)) + tspJavaSysProp = " -Djavax.net.ssl.trustStorePassword=\\${SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD}" + } else if catKsp != "" { + tspJavaSysProp = " -Djavax.net.ssl.trustStorePassword=\\${SOLR_SSL_CLIENT_KEY_STORE_PASSWORD}" + } /* Create a wrapper script like: #!/bin/bash ksp=$(cat $MOUNTED_TLS_DIR/keystore-password) - tsp=$(cat $MOUNTED_TLS_DIR/truststore-password)s + tsp=$(cat $MOUNTED_TLS_DIR/truststore-password) JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.keyStorePassword=${ksp} -Djavax.net.ssl.trustStorePassword=${tsp}" /opt/solr/contrib/prometheus-exporter/bin/solr-exporter $@ @@ -616,7 +625,7 @@ func (tls *TLSCerts) generateTLSInitdbScriptInitContainer() corev1.Container { exportClientKeystorePassword = exportVarFromFileInInitdbWrapperScript("SOLR_SSL_CLIENT_KEY_STORE_PASSWORD", mountedTLSKeystorePasswordPath(mountedDir)) exportClientTruststorePassword = exportVarFromFileInInitdbWrapperScript("SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD", "${SOLR_SSL_CLIENT_KEY_STORE_PASSWORD}") } - if mountedDir.TruststorePasswordFile == "" { + if mountedDir.TruststorePasswordFile != "" { exportClientTruststorePassword = exportVarFromFileInInitdbWrapperScript("SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD", mountedTLSTruststorePasswordPath(mountedDir)) } else if mountedDir.TruststorePassword != "" { exportClientTruststorePassword = "" @@ -642,7 +651,7 @@ func (tls *TLSCerts) generateTLSInitdbScriptInitContainer() corev1.Container { */ return corev1.Container{ - Name: "export-tls-password", + Name: InitdbInitContainer, Image: tls.InitContainerImage.ToImageName(), ImagePullPolicy: tls.InitContainerImage.PullPolicy, Command: []string{"sh", "-c", shCmd}, @@ -652,7 +661,13 @@ func (tls *TLSCerts) generateTLSInitdbScriptInitContainer() corev1.Container { // Helper function for writing a line to the initdb wrapper script that exports an env var sourced from a file func exportVarFromFileInInitdbWrapperScript(varName string, varValue string) string { - return fmt.Sprintf("\\nexport %s=\\`cat %s\\`\\n", varName, varValue) + if strings.HasPrefix(varValue, "${") { + // This is a pass-through variable + return fmt.Sprintf("\\nexport %s=%q\\n", varName, varValue) + } else { + // This is a file to "cat" + return fmt.Sprintf("\\nexport %s=\"$(cat %q)\"\\n", varName, varValue) + } } // Returns an array of Java system properties to configure the TLS certificate used by client applications to call mTLS enabled Solr pods @@ -665,21 +680,18 @@ func (tls *TLSConfig) clientJavaOpts() []string { "-Djavax.net.ssl.trustStoreType=PKCS12", } - if tls.Options.VerifyClientHostname { - // TODO: This is broken in Solr 9.2+ - javaOpts = append(javaOpts, "-Dsolr.jetty.ssl.verifyClientHostName=HTTPS") - } - if tls.Options.PKCS12Secret != nil || (tls.Options.MountedTLSDir != nil && tls.Options.MountedTLSDir.KeystoreFile != "") { javaOpts = append(javaOpts, "-Djavax.net.ssl.keyStore=$(SOLR_SSL_CLIENT_KEY_STORE)") javaOpts = append(javaOpts, "-Djavax.net.ssl.keyStoreType=PKCS12") } - if tls.Options.PKCS12Secret != nil { + hasKeyStorePassword := false + if tls.Options.PKCS12Secret != nil || (tls.Options.MountedTLSDir != nil && tls.Options.MountedTLSDir.KeystorePasswordFile == "" && tls.Options.MountedTLSDir.KeystorePassword != "") { + hasKeyStorePassword = true javaOpts = append(javaOpts, "-Djavax.net.ssl.keyStorePassword=$(SOLR_SSL_CLIENT_KEY_STORE_PASSWORD)") } // else for mounted dir option, the password comes from the wrapper script - if tls.Options.PKCS12Secret != nil || tls.Options.TrustStoreSecret != nil { + if tls.Options.PKCS12Secret != nil || tls.Options.TrustStoreSecret != nil || (tls.Options.MountedTLSDir != nil && tls.Options.MountedTLSDir.TruststorePasswordFile == "" && (tls.Options.MountedTLSDir.TruststorePassword != "" || hasKeyStorePassword)) { javaOpts = append(javaOpts, "-Djavax.net.ssl.trustStorePassword=$(SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD)") } // else for mounted dir option, the password comes from the wrapper script @@ -711,71 +723,6 @@ func (tls *TLSConfig) generatePkcs12InitContainer(imageName string, imagePullPol } } -// Get TLS properties for JAVA_TOOL_OPTIONS and Java system props for configuring the secured probe command; used when -// we call a local command on the Solr pod for the probes instead of using HTTP/HTTPS -func secureProbeTLSJavaToolOpts(solrCloud *solr.SolrCloud) (tlsJavaToolOpts string, tlsJavaSysProps string) { - if solrCloud.Spec.SolrTLS != nil { - // prefer the mounted client cert for probes if provided - tlsDir := solrCloud.Spec.SolrTLS.MountedTLSDir - clientPrefix := "" - if solrCloud.Spec.SolrClientTLS != nil && solrCloud.Spec.SolrClientTLS.MountedTLSDir != nil { - tlsDir = solrCloud.Spec.SolrClientTLS.MountedTLSDir - clientPrefix = "CLIENT_" - } - if tlsDir != nil { - // The keystore passwords are in a file, then we need to cat the file(s) into JAVA_TOOL_OPTIONS - keyStorePassword := "$(cat " + mountedTLSKeystorePasswordPath(tlsDir) + ")" - if tlsDir.KeystorePasswordFile == "" && tlsDir.KeystorePassword != "" { - keyStorePassword = "${SOLR_SSL_" + clientPrefix + "KEY_STORE_PASSWORD}" - } - tlsJavaToolOpts += " -Djavax.net.ssl.keyStorePassword=" + keyStorePassword - trustStorePassword := keyStorePassword - if tlsDir.TruststorePasswordFile != "" { - trustStorePassword = "$(cat " + mountedTLSTruststorePasswordPath(tlsDir) + ")" - } else if tlsDir.TruststorePassword != "" { - trustStorePassword = "${SOLR_SSL_" + clientPrefix + "TRUST_STORE_PASSWORD}" - } - tlsJavaToolOpts += " -Djavax.net.ssl.trustStorePassword=" + trustStorePassword - } - tlsJavaSysProps = secureProbeTLSJavaSysProps(solrCloud) - } - return tlsJavaToolOpts, tlsJavaSysProps -} - -// Get the Java system properties needed to connect to a Solr pod over https -// The values vary depending on whether there is a client cert or just a server cert -// When using the mountedTLSDir option, the keystore / truststore passwords will come from a file instead of env vars -func secureProbeTLSJavaSysProps(solrCloud *solr.SolrCloud) string { - // probe command sends request to "localhost" so skip hostname checking during TLS handshake - tlsJavaSysProps := "-Dsolr.ssl.checkPeerName=false" - - // prefer the client cert for probes if available - if solrCloud.Spec.SolrClientTLS != nil { - tlsJavaSysProps += " -Djavax.net.ssl.trustStore=$SOLR_SSL_CLIENT_TRUST_STORE" - if solrCloud.Spec.SolrClientTLS.MountedTLSDir != nil { - // may not always have a keystore with mountedTLSDir - if solrCloud.Spec.SolrClientTLS.MountedTLSDir.KeystoreFile != "" { - tlsJavaSysProps += " -Djavax.net.ssl.keyStore=$SOLR_SSL_CLIENT_KEY_STORE" - } - } else { - tlsJavaSysProps += " -Djavax.net.ssl.keyStore=$SOLR_SSL_CLIENT_KEY_STORE" - tlsJavaSysProps += " -Djavax.net.ssl.keyStorePassword=$SOLR_SSL_CLIENT_KEY_STORE_PASSWORD" - tlsJavaSysProps += " -Djavax.net.ssl.trustStorePassword=$SOLR_SSL_CLIENT_TRUST_STORE_PASSWORD" - } - } else { - // use the server cert, either from the mounted dir or from envVars sourced from a secret - tlsJavaSysProps += " -Djavax.net.ssl.trustStore=$SOLR_SSL_TRUST_STORE" - tlsJavaSysProps += " -Djavax.net.ssl.keyStore=$SOLR_SSL_KEY_STORE" - - if solrCloud.Spec.SolrTLS.MountedTLSDir == nil { - tlsJavaSysProps += " -Djavax.net.ssl.keyStorePassword=$SOLR_SSL_KEY_STORE_PASSWORD" - tlsJavaSysProps += " -Djavax.net.ssl.trustStorePassword=$SOLR_SSL_TRUST_STORE_PASSWORD" - } // else passwords come through JAVA_TOOL_OPTIONS via cat'ing the mounted files - } - - return tlsJavaSysProps -} - func mountedTLSKeystorePath(tlsDir *solr.MountedTLSDirectory) string { return mountedTLSPath(tlsDir, tlsDir.KeystoreFile, DefaultPkcs12KeystoreFile) } @@ -792,7 +739,7 @@ func mountedTLSTruststorePasswordPath(tlsDir *solr.MountedTLSDirectory) string { path := "" if tlsDir.TruststorePasswordFile != "" { path = mountedTLSPath(tlsDir, tlsDir.TruststorePasswordFile, "") - } else { + } else if tlsDir.TruststorePassword == "" { path = mountedTLSKeystorePasswordPath(tlsDir) } return path diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go index 88949d7e..61e118a3 100644 --- a/controllers/util/solr_util.go +++ b/controllers/util/solr_util.go @@ -279,7 +279,7 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl } } - solrHostName := solrCloud.AdvertisedNodeHost("$(POD_HOSTNAME)") + solrHostName := solrCloud.AdvertisedNodeHost("$(POD_NAME)") solrAdressingPort := solrCloud.NodePort() // Solr can take longer than SOLR_STOP_WAIT to run solr stop, give it a few extra seconds before forcefully killing the pod. @@ -308,6 +308,7 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl Name: "SOLR_NODE_PORT", Value: strconv.Itoa(solrAdressingPort), }, + // POD_HOSTNAME is deprecated and will be removed in a future version. Use POD_NAME instead { Name: "POD_HOSTNAME", ValueFrom: &corev1.EnvVarSource{ @@ -317,6 +318,24 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl }, }, }, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + APIVersion: "v1", + }, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + APIVersion: "v1", + }, + }, + }, { Name: "SOLR_HOST", Value: solrHostName, @@ -608,6 +627,10 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl // 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) + } else { + // If we are not using secure probes, but still using TLS, then make sure that the HOST header is correct when sending liveness and readiness checks. + // Otherwise it is likely that the SNI checks will fail for newer versions of Solr (9.2+) + setHostHeaderForProbesOnSolrCloudStatefulSet(solrCloud, stateful) } return stateful diff --git a/docs/upgrade-notes.md b/docs/upgrade-notes.md index 62d9f758..6f5ad4aa 100644 --- a/docs/upgrade-notes.md +++ b/docs/upgrade-notes.md @@ -41,17 +41,19 @@ If you want to skip versions when upgrading, be sure to check out the [upgrading ### Solr Versions -| Solr Operator Version | `6.6` | `7.7` | `8.0` - `8.5` | `8.6`+ | -|:---------------------:| :---: | :---: | :---: | :---: | -| `v0.2.6` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :x: | -| `v0.2.7` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.2.8` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.3.x` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.4.x` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.5.x` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.6.x` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.7.x` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `v0.8.x` | :grey_question: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Solr Operator Version | `7.7` | `8.0` - `8.10` | `8.11` | `9.0` - `9.3` | `9.4`+ | +|:---------------------:|:------------------:|:------------------:|:------------------:|:------------------------:|:------------------------:| +| `v0.2.7` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| `v0.2.8` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| `v0.3.x` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| `v0.4.x` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| `v0.5.x` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| `v0.6.x` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: :one: | :heavy_check_mark: :one: | +| `v0.7.x` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: :one: | :heavy_check_mark: :one: | +| `v0.8.x` | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: :one: | :heavy_check_mark: | + +**Exceptions** +* :one: `SolrTLS` and `SolrClientTLS` are not supported Please note that this represents basic compatibility with the Solr Operator. There may be options and features that require newer versions of Solr. @@ -112,6 +114,11 @@ _Note that the Helm chart version does not contain a `v` prefix, which the downl ## Upgrade Warnings and Notes ### v0.8.0 +- **The minimum supported Solr version is now 8.11** + If you are unable to use a newer version of Solr, please install the `v0.7.1` version of the Solr Operator. + However, it is strongly suggested to upgrade to newer versions of Solr that are actively supported.q + See the [version compatibility matrix](#solr-versions) for more information. + - **Kubernetes support is now limited to 1.22+.** If you are unable to use a newer version of Kubernetes, please install the `v0.7.1` version of the Solr Operator for use with Kubernetes `1.21`. See the [version compatibility matrix](#kubernetes-versions) for more information. @@ -121,6 +128,8 @@ _Note that the Helm chart version does not contain a `v` prefix, which the downl Set this value to `false` to retain the previous functionality. More information can be found in the [Solr Pod Scale-Down](solr-cloud/scaling.md#solr-pod-scale-down) documentation. +- The `POD_HOSTNAME` envVar in SolrCloud Pods has been deprecated. Use `POD_NAME` instead. + ### v0.7.0 - **Kubernetes support is now limited to 1.21+.** If you are unable to use a newer version of Kubernetes, please install the `v0.6.0` version of the Solr Operator for use with Kubernetes `1.20` and below. diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml index e5479fed..9524b43b 100644 --- a/helm/solr-operator/Chart.yaml +++ b/helm/solr-operator/Chart.yaml @@ -54,6 +54,13 @@ annotations: # Add change log for a single release here. # Allowed syntax is described at: https://artifacthub.io/docs/topics/annotations/helm/#example artifacthub.io/changes: | + - kind: changed + description: The minimum supported version of Solr is now 8.11 + links: + - name: Github Issue + url: https://github.com/apache/solr-operator/issues/616 + - name: Version Compatibility Documentation + url: https://apache.github.io/solr-operator/docs/upgrade-notes.html#solr-versions - kind: changed description: The minimum supported version of Kubernetes is now v1.22 links: diff --git a/helm/solr/Chart.yaml b/helm/solr/Chart.yaml index 03bfc704..3afbca3b 100644 --- a/helm/solr/Chart.yaml +++ b/helm/solr/Chart.yaml @@ -41,16 +41,13 @@ annotations: # Add change log for a single release here. # Allowed syntax is described at: https://artifacthub.io/docs/topics/annotations/helm/#example artifacthub.io/changes: | - - kind: added - description: Addition 1 - links: - - name: Github Issue - url: https://github.com/issue-url - kind: changed - description: Change 2 + description: The minimum supported version of Solr is now 8.11 links: - - name: Github PR - url: https://github.com/pr-url + - name: Github Issue + url: https://github.com/apache/solr-operator/issues/616 + - name: Version Compatibility Documentation + url: https://apache.github.io/solr-operator/docs/upgrade-notes.html#solr-versions artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/recommendations: | - url: https://artifacthub.io/packages/helm/apache-solr/solr-operator diff --git a/tests/e2e/prometheus_exporter_tls_test.go b/tests/e2e/prometheus_exporter_tls_test.go new file mode 100644 index 00000000..a027ffbb --- /dev/null +++ b/tests/e2e/prometheus_exporter_tls_test.go @@ -0,0 +1,177 @@ +/* + * 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" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +/* +There is no need to spin up a lot of SolrClouds for a Prometheus Exporter test, +so all tests will run in the same parallel process and execute serially. +*/ + +var _ = FDescribe("E2E - Prometheus Exporter - TLS ", Ordered, func() { + var ( + solrCloud *solrv1beta1.SolrCloud + + solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter + + solrCollection = "e2e" + ) + + /* + Create a single SolrCloud that all PrometheusExporter tests in this "Describe" will use. + */ + BeforeAll(func(ctx context.Context) { + installSolrIssuer(ctx, testNamespace()) + solrCloud = generateBaseSolrCloudWithSecretTLS(ctx, 2, true) + + solrCloud.Spec.SolrTLS.CheckPeerName = true + + 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 Solr Collection to query metrics for") + createAndQueryCollection(ctx, solrCloud, solrCollection, 1, 2) + }) + + BeforeEach(func() { + solrPrometheusExporter = &solrv1beta1.SolrPrometheusExporter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: testNamespace(), + }, + Spec: solrv1beta1.SolrPrometheusExporterSpec{ + ScrapeInterval: five, + }, + } + }) + + JustBeforeEach(func(ctx context.Context) { + By("creating a SolrPrometheusExporter") + Expect(k8sClient.Create(ctx, solrPrometheusExporter)).To(Succeed()) + + DeferCleanup(func(ctx context.Context) { + deleteAndWait(ctx, solrPrometheusExporter) + }) + + By("waiting for the SolrPrometheusExporter to come up healthy") + solrPrometheusExporter = expectSolrPrometheusExporterWithChecks(ctx, solrPrometheusExporter, func(g Gomega, found *solrv1beta1.SolrPrometheusExporter) { + g.Expect(found.Status.Ready).To(BeTrue(), "The SolrPrometheusExporter should come up healthy") + }) + + By("checking that some base metrics are correct") + checkMetrics(ctx, solrPrometheusExporter, solrCloud, solrCollection) + }) + + FContext("Secret - Solr Reference", func() { + BeforeEach(func() { + solrPrometheusExporter.Spec.SolrReference = solrv1beta1.SolrReference{ + Cloud: &solrv1beta1.SolrCloudReference{ + Name: solrCloud.Name, + }, + SolrTLS: solrCloud.Spec.SolrClientTLS, + } + }) + + // The base metrics tests are run in the "JustBeforeEach" - no additional tests necessary + FIt("Has the correct metrics", func() {}) + }) + + FContext("Secret - Solr ZK Connection String", func() { + BeforeEach(func() { + solrPrometheusExporter.Spec.SolrReference = solrv1beta1.SolrReference{ + Cloud: &solrv1beta1.SolrCloudReference{ + ZookeeperConnectionInfo: &solrCloud.Status.ZookeeperConnectionInfo, + }, + SolrTLS: solrCloud.Spec.SolrClientTLS, + } + }) + + FIt("Has the correct metrics", func() {}) + }) + + FContext("Secret - Solr Host", func() { + BeforeEach(func() { + solrPrometheusExporter.Spec.SolrReference = solrv1beta1.SolrReference{ + Standalone: &solrv1beta1.StandaloneSolrReference{ + Address: solrCloud.Status.InternalCommonAddress + "/solr", + }, + SolrTLS: solrCloud.Spec.SolrClientTLS, + } + }) + + FIt("Has the correct metrics", func() {}) + }) + + /* + These can be uncommented when Cert Manager CSI Driver supports truststore generation + + FContext("MountedDir - Solr Reference", func() { + BeforeEach(func() { + solrPrometheusExporter.Spec.SolrReference = solrv1beta1.SolrReference{ + Cloud: &solrv1beta1.SolrCloudReference{ + Name: solrCloud.Name, + }, + } + addCSITLSToPrometheusExporter(solrPrometheusExporter) + }) + + // The base metrics tests are run in the "JustBeforeEach" - no additional tests necessary + FIt("Has the correct metrics", func() {}) + }) + + FContext("MountedDir - Solr ZK Connection String", func() { + BeforeEach(func() { + solrPrometheusExporter.Spec.SolrReference = solrv1beta1.SolrReference{ + Cloud: &solrv1beta1.SolrCloudReference{ + ZookeeperConnectionInfo: &solrCloud.Status.ZookeeperConnectionInfo, + }, + } + addCSITLSToPrometheusExporter(solrPrometheusExporter) + }) + + FIt("Has the correct metrics", func() {}) + }) + + FContext("MountedDir - Solr Host", func() { + BeforeEach(func() { + solrPrometheusExporter.Spec.SolrReference = solrv1beta1.SolrReference{ + Standalone: &solrv1beta1.StandaloneSolrReference{ + Address: solrCloud.Status.InternalCommonAddress + "/solr", + }, + } + addCSITLSToPrometheusExporter(solrPrometheusExporter) + }) + + FIt("Has the correct metrics", func() {}) + }) + */ +}) diff --git a/tests/e2e/solrcloud_tls_test.go b/tests/e2e/solrcloud_tls_test.go index c814c25f..30b05f2e 100644 --- a/tests/e2e/solrcloud_tls_test.go +++ b/tests/e2e/solrcloud_tls_test.go @@ -20,23 +20,8 @@ package e2e import ( "context" solrv1beta1 "github.com/apache/solr-operator/api/v1beta1" - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/rand" - "k8s.io/utils/pointer" -) - -const ( - solrIssuerName = "solr-issuer" - - secretTlsPasswordKey = "password" - - clientAuthPasswordSecret = "client-auth-password" - clientAuthSecret = "client-auth" ) var _ = FDescribe("E2E - SolrCloud - TLS - Secrets", func() { @@ -96,17 +81,35 @@ var _ = FDescribe("E2E - SolrCloud - TLS - Secrets", func() { FIt("Can run", func() {}) }) - FContext("No Client TLS - VerifyClientHostname", func() { + FContext("No Client TLS - CheckPeerName", func() { BeforeEach(func(ctx context.Context) { solrCloud = generateBaseSolrCloudWithSecretTLS(ctx, 2, false) - solrCloud.Spec.SolrTLS.VerifyClientHostname = true + solrCloud.Spec.SolrTLS.CheckPeerName = true - solrCloud.Spec.SolrOpts = "-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake" + //solrCloud.Spec.SolrOpts = "-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake" + + //solrCloud.Spec.CustomSolrKubeOptions.PodOptions.EnvVariables = + // append(solrCloud.Spec.CustomSolrKubeOptions.PodOptions.EnvVariables, corev1.EnvVar{ + // Name: "SOLR_TOOL_OPTS", + // Value: "-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake", + // }) }) - FIt("Can run", func() {}) + FIt("Can run", func(ctx context.Context) { + By("Checking that using the wrong peer name does not fail") + response, err := callSolrApiInPod( + ctx, + solrCloud, + "get", + "/solr/admin/info/system", + nil, + "localhost", + ) + Expect(err).To(HaveOccurred(), "Error should have occurred while calling Solr API - Bad server hostname for TLS") + Expect(response).To(Or(ContainSubstring("Invalid SNI"), ContainSubstring("doesn't match any of the subject alternative names"), ContainSubstring("No subject alternative DNS name matching")), "Wrong error when calling Solr - Bad hostname for TLS expected") + }) }) FContext("With Client TLS - VerifyClientHostname", func() { @@ -119,7 +122,18 @@ var _ = FDescribe("E2E - SolrCloud - TLS - Secrets", func() { solrCloud.Spec.SolrOpts = "-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake" }) - FIt("Can run", func() {}) + FIt("Can run", func(ctx context.Context) { + By("Checking that using the wrong peer name does not fail") + _, err := callSolrApiInPod( + ctx, + solrCloud, + "get", + "/solr/admin/info/system", + nil, + "localhost", + ) + Expect(err).ToNot(HaveOccurred(), "Error occurred while calling Solr API - Server Hostname checking should not be on") + }) }) FContext("With Client TLS - CheckPeerName", func() { @@ -129,7 +143,7 @@ var _ = FDescribe("E2E - SolrCloud - TLS - Secrets", func() { solrCloud.Spec.SolrTLS.CheckPeerName = true - //solrCloud.Spec.SolrOpts = "-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake" + solrCloud.Spec.SolrOpts = "-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake" }) FIt("Can run", func(ctx context.Context) { @@ -142,8 +156,8 @@ var _ = FDescribe("E2E - SolrCloud - TLS - Secrets", func() { nil, "localhost", ) - Expect(err).To(HaveOccurred(), "Error should have occurred while calling Solr API - Bad hostname for TLS") - Expect(response).To(ContainSubstring("doesn't match any of the subject alternative names"), "Wrong error when calling Solr - Bad hostname for TLS expected") + Expect(err).To(HaveOccurred(), "Error should have occurred while calling Solr API - Bad server hostname for TLS") + Expect(response).To(Or(ContainSubstring("Invalid SNI"), ContainSubstring("doesn't match any of the subject alternative names"), ContainSubstring("No subject alternative DNS name matching")), "Wrong error when calling Solr - Bad hostname for TLS expected") }) }) @@ -232,348 +246,3 @@ var _ = FDescribe("E2E - SolrCloud - TLS - Mounted Dir", func() { // FIt("Can run", func() {}) //}) }) - -func generateBaseSolrCloudWithSecretTLS(ctx context.Context, replicas int, includeClientTLS bool) (solrCloud *solrv1beta1.SolrCloud) { - solrCloud = generateBaseSolrCloud(replicas) - - solrCertSecret, tlsPasswordSecret, clientCertSecret, clientTlsPasswordSecret := generateSolrCert(ctx, solrCloud, includeClientTLS) - - solrCloud.Spec.SolrTLS = &solrv1beta1.SolrTLSOptions{ - PKCS12Secret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: solrCertSecret, - }, - Key: "keystore.p12", - }, - KeyStorePasswordSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: tlsPasswordSecret, - }, - Key: secretTlsPasswordKey, - }, - TrustStoreSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: solrCertSecret, - }, - Key: "truststore.p12", - }, - TrustStorePasswordSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: tlsPasswordSecret, - }, - Key: secretTlsPasswordKey, - }, - } - - if includeClientTLS { - solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{ - PKCS12Secret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientCertSecret, - }, - Key: "keystore.p12", - }, - KeyStorePasswordSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientTlsPasswordSecret, - }, - Key: secretTlsPasswordKey, - }, - TrustStoreSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientCertSecret, - }, - Key: "truststore.p12", - }, - TrustStorePasswordSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientTlsPasswordSecret, - }, - Key: secretTlsPasswordKey, - }, - } - } - return -} - -func generateBaseSolrCloudWithCSITLS(replicas int, csiClientTLS bool, secretClientTLS bool) (solrCloud *solrv1beta1.SolrCloud) { - solrCloud = generateBaseSolrCloud(replicas) - solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes = []solrv1beta1.AdditionalVolume{ - { - Name: "server-tls", - Source: corev1.VolumeSource{ - CSI: &corev1.CSIVolumeSource{ - Driver: "csi.cert-manager.io", - ReadOnly: pointer.Bool(true), - VolumeAttributes: map[string]string{ - "csi.cert-manager.io/issuer-name": solrIssuerName, - "csi.cert-manager.io/common-name": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}", - "csi.cert-manager.io/dns-names": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}.svc.cluster.local," + - solrCloud.Name + "-solrcloud-common.${POD_NAMESPACE}," + - solrCloud.Name + "-solrcloud-common.${POD_NAMESPACE}.svc.cluster.local," + - "${POD_NAME}," + - "${POD_NAME}.${POD_NAMESPACE}," + - "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local", - "csi.cert-manager.io/key-usages": "server auth,digital signature", - "csi.cert-manager.io/pkcs12-enable": "true", - "csi.cert-manager.io/pkcs12-password": "pass", - "csi.cert-manager.io/fs-group": "8983", - }, - }, - }, - DefaultContainerMount: &corev1.VolumeMount{ - ReadOnly: true, - MountPath: "/opt/server-tls", - }, - }, - } - - solrCloud.Spec.SolrTLS = &solrv1beta1.SolrTLSOptions{ - MountedTLSDir: &solrv1beta1.MountedTLSDirectory{ - Path: "/opt/server-tls", - KeystoreFile: "keystore.p12", - KeystorePassword: "pass", - }, - } - - if csiClientTLS { - solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes = append( - solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes, - solrv1beta1.AdditionalVolume{ - Name: "client-tls", - Source: corev1.VolumeSource{ - CSI: &corev1.CSIVolumeSource{ - Driver: "csi.cert-manager.io", - ReadOnly: pointer.Bool(true), - VolumeAttributes: map[string]string{ - "csi.cert-manager.io/issuer-name": solrIssuerName, - "csi.cert-manager.io/common-name": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}", - "csi.cert-manager.io/dns-names": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}.svc.cluster.local," + - "${POD_NAME}," + - "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local", - "csi.cert-manager.io/key-usages": "client auth,digital signature", - "csi.cert-manager.io/pkcs12-enable": "true", - "csi.cert-manager.io/pkcs12-password": "pass", - "csi.cert-manager.io/fs-group": "8983", - }, - }, - }, - DefaultContainerMount: &corev1.VolumeMount{ - ReadOnly: true, - MountPath: "/opt/client-tls", - }, - }) - - solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{ - MountedTLSDir: &solrv1beta1.MountedTLSDirectory{ - Path: "/opt/client-tls", - KeystoreFile: "keystore.p12", - KeystorePassword: "pass", - }, - } - } else if secretClientTLS { - // TODO: It is not currently supported to mix secret and mountedDir TLS. - // This will not work until that support is added. - solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{ - PKCS12Secret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientAuthSecret, - }, - Key: "keystore.p12", - }, - TrustStoreSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientAuthSecret, - }, - Key: "truststore.p12", - }, - TrustStorePasswordSecret: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: clientAuthPasswordSecret, - }, - Key: "password", - }, - } - } - return -} - -func installBootstrapIssuer(ctx context.Context) { - bootstrapIssuer := &certmanagerv1.ClusterIssuer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bootstrap-issuer", - }, - Spec: certmanagerv1.IssuerSpec{ - IssuerConfig: certmanagerv1.IssuerConfig{ - SelfSigned: &certmanagerv1.SelfSignedIssuer{}, - }, - }, - } - Expect(k8sClient.Create(ctx, bootstrapIssuer)).To(Succeed(), "Failed to install SelfSigned ClusterIssuer for bootstrapping CA") - DeferCleanup(func(ctx context.Context) { - Expect(k8sClient.Delete(ctx, bootstrapIssuer)).To(Succeed(), "Failed to delete SelfSigned bootstrapping ClusterIssuer") - }) -} - -func installSolrIssuer(ctx context.Context, namespace string) { - secretName := "solr-ca-key-pair" - clusterCA := &certmanagerv1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "solr-ca", - Namespace: namespace, - }, - Spec: certmanagerv1.CertificateSpec{ - IsCA: true, - CommonName: "solr-ca", - SecretName: secretName, - PrivateKey: &certmanagerv1.CertificatePrivateKey{ - RotationPolicy: certmanagerv1.RotationPolicyNever, - Algorithm: "RSA", - }, - IssuerRef: certmanagermetav1.ObjectReference{ - Name: "bootstrap-issuer", - Kind: "ClusterIssuer", - Group: "cert-manager.io", - }, - }, - } - Expect(k8sClient.Create(ctx, clusterCA)).To(Succeed(), "Failed to install Solr CA for tests") - - namespaceIssuer := &certmanagerv1.Issuer{ - ObjectMeta: metav1.ObjectMeta{ - Name: solrIssuerName, - Namespace: namespace, - }, - Spec: certmanagerv1.IssuerSpec{ - IssuerConfig: certmanagerv1.IssuerConfig{ - CA: &certmanagerv1.CAIssuer{ - SecretName: secretName, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, namespaceIssuer)).To(Succeed(), "Failed to install CA Issuer for issuing test certs in namespace "+namespace) - - expectSecret(ctx, clusterCA, secretName) -} - -func generateSolrCert(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, includeClientTLS bool) (certSecretName string, tlsPasswordSecretName string, clientTLSCertSecretName string, clientTLSPasswordSecretName string) { - // First create a secret to use as a password for the keystore/truststore - tlsPasswordSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: solrCloud.Name + "-keystore-password", - Namespace: solrCloud.Namespace, - }, - StringData: map[string]string{ - secretTlsPasswordKey: rand.String(10), - }, - Type: corev1.SecretTypeOpaque, - } - Expect(k8sClient.Create(ctx, tlsPasswordSecret)).To(Succeed(), "Failed to create secret for tls password in namespace "+solrCloud.Namespace) - - expectSecret(ctx, solrCloud, tlsPasswordSecret.Name) - tlsPasswordSecretName = tlsPasswordSecret.Name - - allDNSNames := make([]string, *solrCloud.Spec.Replicas*2+1) - for _, pod := range solrCloud.GetAllSolrPodNames() { - allDNSNames = append(allDNSNames, pod, solrCloud.InternalNodeUrl(pod, false)) - } - - certSecretName = solrCloud.Name + "-secret-auth" - - solrCert := &certmanagerv1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: solrCloud.Name + "-secret-auth", - Namespace: solrCloud.Namespace, - }, - Spec: certmanagerv1.CertificateSpec{ - CommonName: solrCloud.InternalCommonUrl(false), - DNSNames: allDNSNames, - SecretName: certSecretName, - Keystores: &certmanagerv1.CertificateKeystores{ - PKCS12: &certmanagerv1.PKCS12Keystore{ - Create: true, - PasswordSecretRef: certmanagermetav1.SecretKeySelector{ - LocalObjectReference: certmanagermetav1.LocalObjectReference{ - Name: tlsPasswordSecret.Name, - }, - Key: secretTlsPasswordKey, - }, - }, - }, - IssuerRef: certmanagermetav1.ObjectReference{ - Name: solrIssuerName, - Kind: "Issuer", - Group: "cert-manager.io", - }, - IsCA: false, - Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageServerAuth, certmanagerv1.UsageDigitalSignature}, - PrivateKey: &certmanagerv1.CertificatePrivateKey{ - RotationPolicy: certmanagerv1.RotationPolicyNever, - Algorithm: "RSA", - }, - }, - } - Expect(k8sClient.Create(ctx, solrCert)).To(Succeed(), "Failed to install Solr secret cert for tests") - - expectSecret(ctx, solrCert, certSecretName) - - if includeClientTLS { - // First create a secret to use as a password for the keystore/truststore - clientTlsPasswordSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: solrCloud.Name + "-client-tls-password", - Namespace: solrCloud.Namespace, - }, - StringData: map[string]string{ - secretTlsPasswordKey: rand.String(10), - }, - Type: corev1.SecretTypeOpaque, - } - Expect(k8sClient.Create(ctx, clientTlsPasswordSecret)).To(Succeed(), "Failed to create secret for client tls password in namespace "+solrCloud.Namespace) - - expectSecret(ctx, solrCloud, clientTlsPasswordSecret.Name) - clientTLSPasswordSecretName = clientTlsPasswordSecret.Name - - clientTLSCertSecretName = solrCloud.Name + "-client-tls-secret-auth" - - solrClientCert := &certmanagerv1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: solrCloud.Name + "-client-secret-auth", - Namespace: solrCloud.Namespace, - }, - Spec: certmanagerv1.CertificateSpec{ - CommonName: solrCloud.InternalCommonUrl(false), - DNSNames: allDNSNames, - SecretName: clientTLSCertSecretName, - Keystores: &certmanagerv1.CertificateKeystores{ - PKCS12: &certmanagerv1.PKCS12Keystore{ - Create: true, - PasswordSecretRef: certmanagermetav1.SecretKeySelector{ - LocalObjectReference: certmanagermetav1.LocalObjectReference{ - Name: clientTlsPasswordSecret.Name, - }, - Key: secretTlsPasswordKey, - }, - }, - }, - IssuerRef: certmanagermetav1.ObjectReference{ - Name: solrIssuerName, - Kind: "Issuer", - Group: "cert-manager.io", - }, - IsCA: false, - Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageClientAuth, certmanagerv1.UsageDigitalSignature}, - PrivateKey: &certmanagerv1.CertificatePrivateKey{ - RotationPolicy: certmanagerv1.RotationPolicyNever, - Algorithm: "RSA", - }, - }, - } - Expect(k8sClient.Create(ctx, solrClientCert)).To(Succeed(), "Failed to install Solr clientTLS secret cert for tests") - - expectSecret(ctx, solrClientCert, clientTLSCertSecretName) - } - - return -} diff --git a/tests/e2e/suite_test.go b/tests/e2e/suite_test.go index d6060a39..7ba3e949 100644 --- a/tests/e2e/suite_test.go +++ b/tests/e2e/suite_test.go @@ -341,8 +341,8 @@ func writeAllPodInfoToFiles(ctx context.Context, baseFilename string, pod *corev func writePodLogsToFile(ctx context.Context, filename string, podName string, podNamespace string, startTimeRaw *time.Time, filterLinesWithString string) { logFile, err := os.Create(filename) - defer logFile.Close() Expect(err).ToNot(HaveOccurred(), "Could not open file to save logs: %s", filename) + defer logFile.Close() podLogOpts := corev1.PodLogOptions{} if startTimeRaw != nil { @@ -352,8 +352,8 @@ func writePodLogsToFile(ctx context.Context, filename string, podName string, po req := rawK8sClient.CoreV1().Pods(podNamespace).GetLogs(podName, &podLogOpts) podLogs, logsErr := req.Stream(ctx) - defer podLogs.Close() Expect(logsErr).ToNot(HaveOccurred(), "Could not open stream to fetch pod logs. namespace: %s, pod: %s", podNamespace, podName) + defer podLogs.Close() var logReader io.Reader logReader = podLogs diff --git a/tests/e2e/test_utils_test.go b/tests/e2e/test_utils_test.go index ca5b01c7..4e1db8f5 100644 --- a/tests/e2e/test_utils_test.go +++ b/tests/e2e/test_utils_test.go @@ -455,7 +455,7 @@ func (r *ExecError) Error() string { } func callSolrApiInPod(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, httpMethod string, apiPath string, queryParams map[string]string, hostnameOptional ...string) (response string, err error) { - hostname := "${POD_HOSTNAME}" + hostname := "${POD_NAME}" if len(hostnameOptional) > 0 { hostname = hostnameOptional[0] } @@ -471,6 +471,7 @@ func callSolrApiInPod(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, htt command := []string{ "solr", "api", + "-verbose", "-" + strings.ToLower(httpMethod), fmt.Sprintf( "\"%s://%s:%d%s%s\"", diff --git a/tests/e2e/utils_tls_test.go b/tests/e2e/utils_tls_test.go new file mode 100644 index 00000000..b8efcfad --- /dev/null +++ b/tests/e2e/utils_tls_test.go @@ -0,0 +1,425 @@ +/* + * 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" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/utils/pointer" +) + +const ( + solrIssuerName = "solr-issuer" + + secretTlsPasswordKey = "password" + + clientAuthPasswordSecret = "client-auth-password" + clientAuthSecret = "client-auth" +) + +func generateBaseSolrCloudWithSecretTLS(ctx context.Context, replicas int, includeClientTLS bool) (solrCloud *solrv1beta1.SolrCloud) { + solrCloud = generateBaseSolrCloud(replicas) + + solrCertSecret, tlsPasswordSecret, clientCertSecret, clientTlsPasswordSecret := generateSolrCert(ctx, solrCloud, includeClientTLS) + + solrCloud.Spec.SolrTLS = &solrv1beta1.SolrTLSOptions{ + PKCS12Secret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: solrCertSecret, + }, + Key: "keystore.p12", + }, + KeyStorePasswordSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: tlsPasswordSecret, + }, + Key: secretTlsPasswordKey, + }, + TrustStoreSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: solrCertSecret, + }, + Key: "truststore.p12", + }, + TrustStorePasswordSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: tlsPasswordSecret, + }, + Key: secretTlsPasswordKey, + }, + } + + if includeClientTLS { + solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{ + PKCS12Secret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientCertSecret, + }, + Key: "keystore.p12", + }, + KeyStorePasswordSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientTlsPasswordSecret, + }, + Key: secretTlsPasswordKey, + }, + TrustStoreSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientCertSecret, + }, + Key: "truststore.p12", + }, + TrustStorePasswordSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientTlsPasswordSecret, + }, + Key: secretTlsPasswordKey, + }, + } + } + return +} + +func generateBaseSolrCloudWithCSITLS(replicas int, csiClientTLS bool, secretClientTLS bool) (solrCloud *solrv1beta1.SolrCloud) { + solrCloud = generateBaseSolrCloud(replicas) + solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes = []solrv1beta1.AdditionalVolume{ + { + Name: "server-tls", + Source: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + Driver: "csi.cert-manager.io", + ReadOnly: pointer.Bool(true), + VolumeAttributes: map[string]string{ + "csi.cert-manager.io/issuer-name": solrIssuerName, + "csi.cert-manager.io/common-name": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}", + "csi.cert-manager.io/dns-names": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}.svc.cluster.local," + + solrCloud.Name + "-solrcloud-common.${POD_NAMESPACE}," + + solrCloud.Name + "-solrcloud-common.${POD_NAMESPACE}.svc.cluster.local," + + "${POD_NAME}," + + "${POD_NAME}.${POD_NAMESPACE}," + + "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local", + "csi.cert-manager.io/key-usages": "client auth,server auth,digital signature", + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password": "pass", + "csi.cert-manager.io/fs-group": "8983", + }, + }, + }, + DefaultContainerMount: &corev1.VolumeMount{ + ReadOnly: true, + MountPath: "/opt/server-tls", + }, + }, + } + + solrCloud.Spec.SolrTLS = &solrv1beta1.SolrTLSOptions{ + MountedTLSDir: &solrv1beta1.MountedTLSDirectory{ + Path: "/opt/server-tls", + KeystoreFile: "keystore.p12", + KeystorePassword: "pass", + }, + } + + if csiClientTLS { + solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes = append( + solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes, + solrv1beta1.AdditionalVolume{ + Name: "client-tls", + Source: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + Driver: "csi.cert-manager.io", + ReadOnly: pointer.Bool(true), + VolumeAttributes: map[string]string{ + "csi.cert-manager.io/issuer-name": solrIssuerName, + "csi.cert-manager.io/common-name": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}", + "csi.cert-manager.io/dns-names": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}.svc.cluster.local," + + "${POD_NAME}," + + "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local", + "csi.cert-manager.io/key-usages": "client auth,digital signature", + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password": "pass", + "csi.cert-manager.io/fs-group": "8983", + }, + }, + }, + DefaultContainerMount: &corev1.VolumeMount{ + ReadOnly: true, + MountPath: "/opt/client-tls", + }, + }) + + solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{ + MountedTLSDir: &solrv1beta1.MountedTLSDirectory{ + Path: "/opt/client-tls", + KeystoreFile: "keystore.p12", + KeystorePassword: "pass", + }, + } + } else if secretClientTLS { + // TODO: It is not currently supported to mix secret and mountedDir TLS. + // This will not work until that support is added. + solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{ + PKCS12Secret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientAuthSecret, + }, + Key: "keystore.p12", + }, + TrustStoreSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientAuthSecret, + }, + Key: "truststore.p12", + }, + TrustStorePasswordSecret: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: clientAuthPasswordSecret, + }, + Key: "password", + }, + } + } + return +} + +func addCSITLSToPrometheusExporter(prometheusExporter *solrv1beta1.SolrPrometheusExporter) { + if prometheusExporter.Spec.CustomKubeOptions.PodOptions == nil { + prometheusExporter.Spec.CustomKubeOptions.PodOptions = &solrv1beta1.PodOptions{} + } + prometheusExporter.Spec.CustomKubeOptions.PodOptions.Volumes = []solrv1beta1.AdditionalVolume{ + { + Name: "server-tls", + Source: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + Driver: "csi.cert-manager.io", + ReadOnly: pointer.Bool(true), + VolumeAttributes: map[string]string{ + "csi.cert-manager.io/issuer-name": solrIssuerName, + "csi.cert-manager.io/common-name": "${POD_NAME}", + "csi.cert-manager.io/dns-names": "${POD_NAME}.${POD_NAMESPACE}," + + "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local", + "csi.cert-manager.io/key-usages": "client auth,digital signature", + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password": "pass", + "csi.cert-manager.io/fs-group": "8983", + }, + }, + }, + DefaultContainerMount: &corev1.VolumeMount{ + ReadOnly: true, + MountPath: "/opt/client-tls", + }, + }, + } + + prometheusExporter.Spec.SolrReference.SolrTLS = &solrv1beta1.SolrTLSOptions{ + MountedTLSDir: &solrv1beta1.MountedTLSDirectory{ + Path: "/opt/client-tls", + KeystoreFile: "keystore.p12", + KeystorePassword: "pass", + }, + } + return +} + +func installBootstrapIssuer(ctx context.Context) { + bootstrapIssuer := &certmanagerv1.ClusterIssuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bootstrap-issuer", + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + SelfSigned: &certmanagerv1.SelfSignedIssuer{}, + }, + }, + } + Expect(k8sClient.Create(ctx, bootstrapIssuer)).To(Succeed(), "Failed to install SelfSigned ClusterIssuer for bootstrapping CA") + DeferCleanup(func(ctx context.Context) { + Expect(k8sClient.Delete(ctx, bootstrapIssuer)).To(Succeed(), "Failed to delete SelfSigned bootstrapping ClusterIssuer") + }) +} + +func installSolrIssuer(ctx context.Context, namespace string) { + secretName := "solr-ca-key-pair" + clusterCA := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "solr-ca", + Namespace: namespace, + }, + Spec: certmanagerv1.CertificateSpec{ + IsCA: true, + CommonName: "solr-ca", + SecretName: secretName, + PrivateKey: &certmanagerv1.CertificatePrivateKey{ + RotationPolicy: certmanagerv1.RotationPolicyNever, + Algorithm: "RSA", + }, + IssuerRef: certmanagermetav1.ObjectReference{ + Name: "bootstrap-issuer", + Kind: "ClusterIssuer", + Group: "cert-manager.io", + }, + }, + } + Expect(k8sClient.Create(ctx, clusterCA)).To(Succeed(), "Failed to install Solr CA for tests") + + namespaceIssuer := &certmanagerv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrIssuerName, + Namespace: namespace, + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + CA: &certmanagerv1.CAIssuer{ + SecretName: secretName, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, namespaceIssuer)).To(Succeed(), "Failed to install CA Issuer for issuing test certs in namespace "+namespace) + + expectSecret(ctx, clusterCA, secretName) +} + +func generateSolrCert(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, includeClientTLS bool) (certSecretName string, tlsPasswordSecretName string, clientTLSCertSecretName string, clientTLSPasswordSecretName string) { + // First create a secret to use as a password for the keystore/truststore + tlsPasswordSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrCloud.Name + "-keystore-password", + Namespace: solrCloud.Namespace, + }, + StringData: map[string]string{ + secretTlsPasswordKey: rand.String(10), + }, + Type: corev1.SecretTypeOpaque, + } + Expect(k8sClient.Create(ctx, tlsPasswordSecret)).To(Succeed(), "Failed to create secret for tls password in namespace "+solrCloud.Namespace) + + expectSecret(ctx, solrCloud, tlsPasswordSecret.Name) + tlsPasswordSecretName = tlsPasswordSecret.Name + + allDNSNames := make([]string, *solrCloud.Spec.Replicas*2+1) + for _, pod := range solrCloud.GetAllSolrPodNames() { + allDNSNames = append(allDNSNames, pod, solrCloud.InternalNodeUrl(pod, false)) + } + + certSecretName = solrCloud.Name + "-secret-auth" + + solrCert := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrCloud.Name + "-secret-auth", + Namespace: solrCloud.Namespace, + }, + Spec: certmanagerv1.CertificateSpec{ + CommonName: solrCloud.InternalCommonUrl(false), + DNSNames: allDNSNames, + SecretName: certSecretName, + Keystores: &certmanagerv1.CertificateKeystores{ + PKCS12: &certmanagerv1.PKCS12Keystore{ + Create: true, + PasswordSecretRef: certmanagermetav1.SecretKeySelector{ + LocalObjectReference: certmanagermetav1.LocalObjectReference{ + Name: tlsPasswordSecret.Name, + }, + Key: secretTlsPasswordKey, + }, + }, + }, + IssuerRef: certmanagermetav1.ObjectReference{ + Name: solrIssuerName, + Kind: "Issuer", + Group: "cert-manager.io", + }, + IsCA: false, + Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageServerAuth, certmanagerv1.UsageDigitalSignature}, + PrivateKey: &certmanagerv1.CertificatePrivateKey{ + RotationPolicy: certmanagerv1.RotationPolicyNever, + Algorithm: "RSA", + }, + }, + } + Expect(k8sClient.Create(ctx, solrCert)).To(Succeed(), "Failed to install Solr secret cert for tests") + + expectSecret(ctx, solrCert, certSecretName) + + if includeClientTLS { + // First create a secret to use as a password for the keystore/truststore + clientTlsPasswordSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrCloud.Name + "-client-tls-password", + Namespace: solrCloud.Namespace, + }, + StringData: map[string]string{ + secretTlsPasswordKey: rand.String(10), + }, + Type: corev1.SecretTypeOpaque, + } + Expect(k8sClient.Create(ctx, clientTlsPasswordSecret)).To(Succeed(), "Failed to create secret for client tls password in namespace "+solrCloud.Namespace) + + expectSecret(ctx, solrCloud, clientTlsPasswordSecret.Name) + clientTLSPasswordSecretName = clientTlsPasswordSecret.Name + + clientTLSCertSecretName = solrCloud.Name + "-client-tls-secret-auth" + + solrClientCert := &certmanagerv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: solrCloud.Name + "-client-secret-auth", + Namespace: solrCloud.Namespace, + }, + Spec: certmanagerv1.CertificateSpec{ + CommonName: solrCloud.InternalCommonUrl(false), + DNSNames: allDNSNames, + SecretName: clientTLSCertSecretName, + Keystores: &certmanagerv1.CertificateKeystores{ + PKCS12: &certmanagerv1.PKCS12Keystore{ + Create: true, + PasswordSecretRef: certmanagermetav1.SecretKeySelector{ + LocalObjectReference: certmanagermetav1.LocalObjectReference{ + Name: clientTlsPasswordSecret.Name, + }, + Key: secretTlsPasswordKey, + }, + }, + }, + IssuerRef: certmanagermetav1.ObjectReference{ + Name: solrIssuerName, + Kind: "Issuer", + Group: "cert-manager.io", + }, + IsCA: false, + Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageClientAuth, certmanagerv1.UsageDigitalSignature}, + PrivateKey: &certmanagerv1.CertificatePrivateKey{ + RotationPolicy: certmanagerv1.RotationPolicyNever, + Algorithm: "RSA", + }, + }, + } + Expect(k8sClient.Create(ctx, solrClientCert)).To(Succeed(), "Failed to install Solr clientTLS secret cert for tests") + + expectSecret(ctx, solrClientCert, clientTLSCertSecretName) + } + + return +}