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 })