github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/tls_utils_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package apps
    21  
    22  import (
    23  	"context"
    24  	"strings"
    25  
    26  	. "github.com/onsi/ginkgo/v2"
    27  	. "github.com/onsi/gomega"
    28  
    29  	corev1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    35  	"github.com/1aal/kubeblocks/controllers/apps/components"
    36  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    37  	"github.com/1aal/kubeblocks/pkg/constant"
    38  	"github.com/1aal/kubeblocks/pkg/controller/plan"
    39  	"github.com/1aal/kubeblocks/pkg/generics"
    40  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    41  	testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    42  )
    43  
    44  var _ = Describe("TLS self-signed cert function", func() {
    45  	const (
    46  		clusterDefName      = "test-clusterdef-tls"
    47  		clusterVersionName  = "test-clusterversion-tls"
    48  		clusterNamePrefix   = "test-cluster"
    49  		statefulCompDefName = "mysql"
    50  		statefulCompName    = "mysql"
    51  		mysqlContainerName  = "mysql"
    52  		configSpecName      = "mysql-config-tpl"
    53  	)
    54  
    55  	ctx := context.Background()
    56  
    57  	// Cleanups
    58  
    59  	cleanEnv := func() {
    60  		// must wait until resources deleted and no longer exist before the testcases start,
    61  		// otherwise if later it needs to create some new resource objects with the same name,
    62  		// in race conditions, it will find the existence of old objects, resulting failure to
    63  		// create the new objects.
    64  		By("clean resources")
    65  
    66  		// delete cluster(and all dependent sub-resources), clusterversion and clusterdef
    67  		testapps.ClearClusterResources(&testCtx)
    68  
    69  		// delete rest configurations
    70  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    71  		// non-namespaced
    72  		testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml)
    73  		testapps.ClearResources(&testCtx, generics.BackupPolicyTemplateSignature, ml)
    74  	}
    75  
    76  	BeforeEach(cleanEnv)
    77  
    78  	AfterEach(cleanEnv)
    79  
    80  	// Testcases
    81  	// Scenarios
    82  
    83  	Context("tls is enabled/disabled", func() {
    84  		BeforeEach(func() {
    85  			configMapObj := testapps.CheckedCreateCustomizedObj(&testCtx,
    86  				"resources/mysql-tls-config-template.yaml",
    87  				&corev1.ConfigMap{},
    88  				testCtx.UseDefaultNamespace(),
    89  				testapps.WithAnnotations(constant.CMInsEnableRerenderTemplateKey, "true"))
    90  
    91  			configConstraintObj := testapps.CheckedCreateCustomizedObj(&testCtx,
    92  				"resources/mysql-config-constraint.yaml",
    93  				&appsv1alpha1.ConfigConstraint{})
    94  
    95  			By("Create a clusterDef obj")
    96  			testapps.NewClusterDefFactory(clusterDefName).
    97  				SetConnectionCredential(map[string]string{"username": "root", "password": ""}, nil).
    98  				AddComponentDef(testapps.ConsensusMySQLComponent, statefulCompDefName).
    99  				AddConfigTemplate(configSpecName, configMapObj.Name, configConstraintObj.Name, testCtx.DefaultNamespace, testapps.ConfVolumeName).
   100  				AddContainerEnv(mysqlContainerName, corev1.EnvVar{Name: "MYSQL_ALLOW_EMPTY_PASSWORD", Value: "yes"}).
   101  				CheckedCreate(&testCtx).GetObject()
   102  
   103  			By("Create a clusterVersion obj")
   104  			testapps.NewClusterVersionFactory(clusterVersionName, clusterDefName).
   105  				AddComponentVersion(statefulCompDefName).AddContainerShort(mysqlContainerName, testapps.ApeCloudMySQLImage).
   106  				CheckedCreate(&testCtx).GetObject()
   107  
   108  		})
   109  
   110  		// Context("when issuer is KubeBlocks", func() {
   111  		// 	var tlsIssuer *appsv1alpha1.Issuer
   112  		//
   113  		// 	BeforeEach(func() {
   114  		// 		tlsIssuer = &appsv1alpha1.Issuer{
   115  		// 			Name: appsv1alpha1.IssuerKubeBlocks,
   116  		// 		}
   117  		// 	})
   118  		//
   119  		// 	It("should create/delete the tls cert Secret", func() {
   120  		//
   121  		// 		// REVIEW: do review this test setup
   122  		// 		//  In [AfterEach] at: /Users/nashtsai/go/src/github.com/1aal/kubeblocks/pkg/testutil/apps/common_util.go:323
   123  		// 		// Assertion in callback at /Users/nashtsai/go/src/github.com/1aal/kubeblocks/pkg/testutil/apps/common_util.go:322 failed:
   124  		// 		// Expected
   125  		// 		// <[]v1.StatefulSet | len:1, cap:1>:
   126  		// 		// 	to be empty
   127  		// 		// 	In [AfterEach] at:
   128  		//
   129  		// 		By("create a cluster obj")
   130  		// 		clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace,
   131  		// 			clusterNamePrefix, clusterDefName, clusterVersionName).
   132  		// 			WithRandomName().
   133  		// 			AddComponentDef(statefulCompName, statefulCompDefName).
   134  		// 			SetReplicas(3).
   135  		// 			SetTLS(true).
   136  		// 			SetIssuer(tlsIssuer).
   137  		// 			Create(&testCtx).
   138  		// 			GetObject()
   139  		//
   140  		// 		clusterKey := client.ObjectKeyFromObject(clusterObj)
   141  		//
   142  		// 		By("Waiting for the cluster enter creating phase")
   143  		// 		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   144  		// 		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   145  		//
   146  		// 		By("By inspect that TLS cert. secret")
   147  		// 		ns := clusterObj.Namespace
   148  		// 		name := plan.GenerateTLSSecretName(clusterObj.Name, statefulCompName)
   149  		// 		nsName := types.NamespacedName{Namespace: ns, Name: name}
   150  		// 		secret := &corev1.Secret{}
   151  		//
   152  		// 		// REVIEW: Caught following:
   153  		// 		// [FAILED] Timed out after 10.000s.
   154  		// 		// 	Expected success, but got an error:
   155  		// 		// <*errors.StatusError | 0x14001dc46e0>: {
   156  		// 		// ErrStatus: {
   157  		// 		// TypeMeta: {Kind: "", APIVersion: ""},
   158  		// 		// ListMeta: {
   159  		// 		// SelfLink: "",
   160  		// 		// 	ResourceVersion: "",
   161  		// 		// 		Continue: "",
   162  		// 		// 		RemainingItemCount: nil,
   163  		// 		// },
   164  		// 		// Status: "Failure",
   165  		// 		// 	Message: "secrets \"test-clusterlmgbpe-mysql-tls-certs\" not found",
   166  		// 		// 		Reason: "NotFound",
   167  		// 		// 		Details: {
   168  		// 		// 	Name: "test-clusterlmgbpe-mysql-tls-certs",
   169  		// 		// 		Group: "",
   170  		// 		// 			Kind: "secrets",
   171  		// 		// 			UID: "",
   172  		// 		// 			Causes: nil,
   173  		// 		// 			RetryAfterSeconds: 0,
   174  		// 		// 	},
   175  		// 		// Code: 404,
   176  		// 		// },
   177  		// 		// }
   178  		// 		// secrets "test-clusterlmgbpe-mysql-tls-certs" not found
   179  		//
   180  		// 		Eventually(k8sClient.Get(ctx, nsName, secret)).Should(Succeed())
   181  		//
   182  		// 		By("Checking volume & volumeMount settings in podSpec")
   183  		// 		stsList := testk8s.ListAndCheckStatefulSet(&testCtx, client.ObjectKeyFromObject(clusterObj))
   184  		// 		sts := stsList.Items[0]
   185  		// 		hasTLSVolume := false
   186  		// 		for _, volume := range sts.Spec.Template.Spec.Volumes {
   187  		// 			if volume.Name == builder.VolumeName {
   188  		// 				hasTLSVolume = true
   189  		// 				break
   190  		// 			}
   191  		// 		}
   192  		// 		Expect(hasTLSVolume).Should(BeTrue())
   193  		// 		for _, container := range sts.Spec.Template.Spec.Containers {
   194  		// 			hasTLSVolumeMount := false
   195  		// 			for _, mount := range container.VolumeMounts {
   196  		// 				if mount.Name == builder.VolumeName {
   197  		// 					hasTLSVolumeMount = true
   198  		// 					break
   199  		// 				}
   200  		// 			}
   201  		// 			Expect(hasTLSVolumeMount).Should(BeTrue())
   202  		// 		}
   203  		// 	})
   204  		// })
   205  
   206  		Context("when issuer is UserProvided", func() {
   207  			var userProvidedTLSSecretObj *corev1.Secret
   208  
   209  			BeforeEach(func() {
   210  				// prepare self provided tls certs secret
   211  				var err error
   212  				userProvidedTLSSecretObj, err = plan.ComposeTLSSecret(testCtx.DefaultNamespace, "test", "self-provided")
   213  				Expect(err).Should(BeNil())
   214  				Expect(k8sClient.Create(ctx, userProvidedTLSSecretObj)).Should(Succeed())
   215  			})
   216  			AfterEach(func() {
   217  				// delete self provided tls certs secret
   218  				Expect(k8sClient.Delete(ctx, userProvidedTLSSecretObj)).Should(Succeed())
   219  				Eventually(func() bool {
   220  					err := k8sClient.Get(ctx,
   221  						client.ObjectKeyFromObject(userProvidedTLSSecretObj),
   222  						userProvidedTLSSecretObj)
   223  					return apierrors.IsNotFound(err)
   224  				}).Should(BeTrue())
   225  			})
   226  			It("should create the cluster when secret referenced exist", func() {
   227  				tlsIssuer := &appsv1alpha1.Issuer{
   228  					Name: appsv1alpha1.IssuerUserProvided,
   229  					SecretRef: &appsv1alpha1.TLSSecretRef{
   230  						Name: userProvidedTLSSecretObj.Name,
   231  						CA:   "ca.crt",
   232  						Cert: "tls.crt",
   233  						Key:  "tls.key",
   234  					},
   235  				}
   236  				By("create cluster obj")
   237  				clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix, clusterDefName, clusterVersionName).
   238  					WithRandomName().
   239  					AddComponent(statefulCompName, statefulCompDefName).
   240  					SetReplicas(3).
   241  					SetTLS(true).
   242  					SetIssuer(tlsIssuer).
   243  					Create(&testCtx).
   244  					GetObject()
   245  				Eventually(k8sClient.Get(ctx,
   246  					client.ObjectKeyFromObject(clusterObj),
   247  					clusterObj)).
   248  					Should(Succeed())
   249  			})
   250  
   251  			// REVIEW/TODO: following test setup needs to be revised, the setup looks like
   252  			//   hacking test result, it's expected that cluster.status.observerGeneration=1
   253  			//   with error conditions
   254  			// It("should not create the cluster when secret referenced not exist", func() {
   255  			// 	tlsIssuer := &appsv1alpha1.Issuer{
   256  			// 		Name: appsv1alpha1.IssuerUserProvided,
   257  			// 		SecretRef: &appsv1alpha1.TLSSecretRef{
   258  			// 			Name: "secret-name-not-exist",
   259  			// 			CA:   "ca.crt",
   260  			// 			Cert: "tls.crt",
   261  			// 			Key:  "tls.key",
   262  			// 		},
   263  			// 	}
   264  			// 	By("create cluster obj")
   265  			// 	clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix, clusterDefName, clusterVersionName).
   266  			// 		WithRandomName().
   267  			// 		AddComponentDef(statefulCompName, statefulCompDefName).
   268  			// 		SetReplicas(3).
   269  			// 		SetTLS(true).
   270  			// 		SetIssuer(tlsIssuer).
   271  			// 		Create(&testCtx).
   272  			// 		GetObject()
   273  
   274  			// 	clusterKey := client.ObjectKeyFromObject(clusterObj)
   275  			// By("Waiting for the cluster enter creating phase")
   276  			// Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   277  			// Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(BeEquivalentTo(appsv1alpha1.CreatingPhase))
   278  
   279  			// 	By("By check cluster status.phase=ConditionsError")
   280  			// 	Eventually(testapps.GetClusterPhase(&testCtx, client.ObjectKeyFromObject(clusterObj))).
   281  			// 		Should(Equal(appsv1alpha1.ConditionsErrorPhase))
   282  			// })
   283  		})
   284  
   285  		Context("when switch between disabled and enabled", func() {
   286  			It("should handle tls settings properly", func() {
   287  				By("create cluster with tls disabled")
   288  				clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix, clusterDefName, clusterVersionName).
   289  					WithRandomName().
   290  					AddComponent(statefulCompName, statefulCompDefName).
   291  					SetReplicas(3).
   292  					SetTLS(false).
   293  					Create(&testCtx).
   294  					GetObject()
   295  				clusterKey := client.ObjectKeyFromObject(clusterObj)
   296  				Eventually(k8sClient.Get(ctx, clusterKey, clusterObj)).Should(Succeed())
   297  				Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   298  				Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   299  
   300  				rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   301  				sts := *components.ConvertRSMToSTS(&rsmList.Items[0])
   302  				cd := &appsv1alpha1.ClusterDefinition{}
   303  				Expect(k8sClient.Get(ctx, types.NamespacedName{Name: clusterDefName, Namespace: testCtx.DefaultNamespace}, cd)).Should(Succeed())
   304  				cmName := cfgcore.GetInstanceCMName(&sts, &cd.Spec.ComponentDefs[0].ConfigSpecs[0].ComponentTemplateSpec)
   305  				cmKey := client.ObjectKey{Namespace: sts.Namespace, Name: cmName}
   306  				hasTLSSettings := func() bool {
   307  					cm := &corev1.ConfigMap{}
   308  					Expect(k8sClient.Get(ctx, cmKey, cm)).Should(Succeed())
   309  					tlsKeyWord := plan.GetTLSKeyWord("mysql")
   310  					for _, cfgFile := range cm.Data {
   311  						index := strings.Index(cfgFile, tlsKeyWord)
   312  						if index >= 0 {
   313  							return true
   314  						}
   315  					}
   316  					return false
   317  				}
   318  
   319  				Eventually(hasTLSSettings).Should(BeFalse())
   320  
   321  				By("update tls to enabled")
   322  				Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterObj), clusterObj)).Should(Succeed())
   323  				patch := client.MergeFrom(clusterObj.DeepCopy())
   324  				clusterObj.Spec.ComponentSpecs[0].TLS = true
   325  				clusterObj.Spec.ComponentSpecs[0].Issuer = &appsv1alpha1.Issuer{Name: appsv1alpha1.IssuerKubeBlocks}
   326  				Expect(k8sClient.Patch(ctx, clusterObj, patch)).Should(Succeed())
   327  
   328  				conf := &appsv1alpha1.Configuration{}
   329  				confKey := client.ObjectKey{Namespace: sts.Namespace, Name: cfgcore.GenerateComponentConfigurationName(clusterObj.Name, clusterObj.Spec.ComponentSpecs[0].Name)}
   330  				Expect(k8sClient.Get(ctx, confKey, conf)).Should(Succeed())
   331  				patch2 := client.MergeFrom(conf.DeepCopy())
   332  				conf.Spec.ConfigItemDetails[0].Version = "v1"
   333  				Expect(k8sClient.Patch(ctx, conf, patch2)).Should(Succeed())
   334  				Eventually(hasTLSSettings).Should(BeTrue())
   335  
   336  				By("update tls to disabled")
   337  				patch = client.MergeFrom(clusterObj.DeepCopy())
   338  				clusterObj.Spec.ComponentSpecs[0].TLS = false
   339  				clusterObj.Spec.ComponentSpecs[0].Issuer = nil
   340  				Expect(k8sClient.Patch(ctx, clusterObj, patch)).Should(Succeed())
   341  
   342  				Expect(k8sClient.Get(ctx, confKey, conf)).Should(Succeed())
   343  				patch2 = client.MergeFrom(conf.DeepCopy())
   344  				conf.Spec.ConfigItemDetails[0].Version = "v2"
   345  				Expect(k8sClient.Patch(ctx, conf, patch2)).Should(Succeed())
   346  
   347  				Eventually(hasTLSSettings).Should(BeFalse())
   348  			})
   349  		})
   350  	})
   351  })