github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/update/certac/certac_test.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package certac 5 6 import ( 7 "bytes" 8 "encoding/base64" 9 "fmt" 10 "reflect" 11 "strings" 12 "time" 13 14 "github.com/Jeffail/gabs/v2" 15 . "github.com/onsi/ginkgo/v2" 16 "github.com/onsi/gomega" 17 aocnst "github.com/verrazzano/verrazzano/application-operator/constants" 18 "github.com/verrazzano/verrazzano/cluster-operator/controllers/vmc" 19 "github.com/verrazzano/verrazzano/pkg/constants" 20 "github.com/verrazzano/verrazzano/pkg/mcconstants" 21 vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1" 22 pocnst "github.com/verrazzano/verrazzano/platform-operator/constants" 23 "github.com/verrazzano/verrazzano/tests/e2e/multicluster" 24 "github.com/verrazzano/verrazzano/tests/e2e/pkg" 25 "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework" 26 "github.com/verrazzano/verrazzano/tests/e2e/pkg/update" 27 "sigs.k8s.io/yaml" 28 ) 29 30 var ( 31 t = framework.NewTestFramework("update fluentd external opensearch") 32 adminCluster *multicluster.Cluster 33 managedClusters []*multicluster.Cluster 34 originalCM *vzapi.CertManagerComponent 35 originalFluentd *vzapi.FluentdComponent 36 waitTimeout = 10 * time.Minute 37 pollingInterval = 5 * time.Second 38 ) 39 40 type CertModifier struct { 41 CertManager *vzapi.CertManagerComponent 42 } 43 44 func (u *CertModifier) ModifyCR(cr *vzapi.Verrazzano) { 45 cr.Spec.Components.CertManager = u.CertManager 46 } 47 48 var beforeSuite = t.BeforeSuiteFunc(func() { 49 adminCluster = multicluster.AdminCluster() 50 managedClusters = multicluster.ManagedClusters() 51 adminVZ := adminCluster.GetCR(true) 52 originalCM = adminVZ.Spec.Components.CertManager 53 if !isDefaultCM(originalCM) { 54 Fail("TestAdminClusterCertManagerUpdate requires default CertManager in AdminCluster") 55 } 56 originalFluentd = adminVZ.Spec.Components.Fluentd 57 verifyRegistration() 58 }) 59 60 var _ = BeforeSuite(beforeSuite) 61 62 var afterSuite = t.AfterSuiteFunc(func() { 63 }) 64 65 var _ = AfterSuite(afterSuite) 66 67 var _ = t.Describe("Update admin-cluster cert-manager", Label("f:platform-lcm.update"), func() { 68 t.Describe("multicluster cert-manager verify", Label("f:platform-lcm.multicluster-verify"), func() { 69 t.It("admin-cluster cert-manager custom CA", func() { 70 start := time.Now() 71 oldIngressCaCrt := updateAdminClusterCA() 72 verifyCaSync(oldIngressCaCrt) 73 // verify new logs are flowing after updating admin cert 74 verifyManagedFluentd(start) 75 }) 76 }) 77 t.Describe("multicluster cert-manager verify cleanup", Label("f:platform-lcm.multicluster-verify"), func() { 78 t.It("admin-cluster cert-manager revert to default self-signed CA", func() { 79 start := time.Now() 80 oldIngressCaCrt := revertToDefaultCertManager() 81 verifyCaSync(oldIngressCaCrt) 82 verifyManagedFluentd(start) 83 }) 84 }) 85 }) 86 87 func updateAdminClusterCA() string { 88 oldIngressCaCrt := adminCluster. 89 GetSecretDataAsString(constants.VerrazzanoSystemNamespace, pocnst.VerrazzanoIngressSecret, mcconstants.CaCrtKey) 90 genCA := adminCluster.GenerateCA() 91 newCM := &vzapi.CertManagerComponent{ 92 Certificate: vzapi.Certificate{CA: vzapi.CA{SecretName: genCA, ClusterResourceNamespace: constants.CertManagerNamespace}}, 93 } 94 m := &CertModifier{CertManager: newCM} 95 update.RetryUpdate(m, adminCluster.KubeConfigPath, true, pollingInterval, waitTimeout) 96 return oldIngressCaCrt 97 } 98 99 func isDefaultCM(cm *vzapi.CertManagerComponent) bool { 100 return cm == nil || reflect.DeepEqual(*cm, vzapi.CertManagerComponent{}) 101 } 102 103 func isDefaultFluentd(fl *vzapi.FluentdComponent) bool { 104 return fl == nil || reflect.DeepEqual(*fl, vzapi.FluentdComponent{}) 105 } 106 107 func revertToDefaultCertManager() string { 108 oldIngressCaCrt := adminCluster. 109 GetSecretDataAsString(constants.VerrazzanoSystemNamespace, pocnst.VerrazzanoIngressSecret, mcconstants.CaCrtKey) 110 m := &CertModifier{CertManager: originalCM} 111 update.RetryUpdate(m, adminCluster.KubeConfigPath, true, pollingInterval, waitTimeout) 112 return oldIngressCaCrt 113 } 114 115 func verifyCaSync(oldIngressCaCrt string) { 116 newIngressCaCrt := verifyAdminClusterIngressCA(oldIngressCaCrt) 117 reapplyManagedClusterRegManifest(newIngressCaCrt) 118 verifyCaSyncToManagedClusters(newIngressCaCrt) 119 } 120 121 func verifyAdminClusterIngressCA(oldIngressCaCrt string) string { 122 start := time.Now() 123 gomega.Eventually(func() bool { 124 newIngressCaCrt := adminCluster. 125 GetSecretDataAsString(constants.VerrazzanoSystemNamespace, pocnst.VerrazzanoIngressSecret, mcconstants.CaCrtKey) 126 if newIngressCaCrt == oldIngressCaCrt { 127 pkg.Log(pkg.Error, fmt.Sprintf("%v of %v is not updated", pocnst.VerrazzanoIngressSecret, adminCluster.Name)) 128 } else { 129 pkg.Log(pkg.Error, fmt.Sprintf("%v of %v took %v updated", pocnst.VerrazzanoIngressSecret, adminCluster.Name, time.Since(start))) 130 } 131 return newIngressCaCrt != oldIngressCaCrt 132 }).WithTimeout(waitTimeout).WithPolling(pollingInterval).Should(gomega.BeTrue(), fmt.Sprintf("Fail updating ingress %v CA", adminCluster.Name)) 133 return adminCluster. 134 GetSecretDataAsString(constants.VerrazzanoSystemNamespace, pocnst.VerrazzanoIngressSecret, mcconstants.CaCrtKey) 135 } 136 137 func verifyCaSyncToManagedClusters(admCaCrt string) { 138 for _, managedCluster := range managedClusters { 139 verifyManagedClusterRegistration(managedCluster, admCaCrt, mcconstants.AdminCaBundleKey) 140 verifyManagedClusterAdminKubeconfig(managedCluster, admCaCrt, mcconstants.KubeconfigKey) 141 if isDefaultFluentd(originalFluentd) { 142 verifyManagedClusterRegistration(managedCluster, admCaCrt, mcconstants.ESCaBundleKey) 143 } 144 } 145 } 146 147 func verifyManagedClusterAdminKubeconfig(managedCluster *multicluster.Cluster, admCaCrt, kubeconfigKey string) { 148 start := time.Now() 149 gomega.Eventually(func() error { 150 newKubeconfig := managedCluster. 151 GetSecretDataAsString(constants.VerrazzanoSystemNamespace, aocnst.MCAgentSecret, kubeconfigKey) 152 jsonKubeconfig, err := yaml.YAMLToJSON([]byte(newKubeconfig)) 153 if err != nil { 154 fullErr := fmt.Errorf("Failed converting admin kubeconfig to JSON: %v", err) 155 pkg.Log(pkg.Error, fullErr.Error()) 156 return fullErr 157 } 158 parsedKubeconfig, err := gabs.ParseJSON(jsonKubeconfig) 159 if err != nil { 160 return fmt.Errorf("failed parsing admin kubeconfig JSON: %v", err) 161 } 162 newCaCrtData := parsedKubeconfig.Search("clusters", "0", "cluster", "certificate-authority-data").Data() 163 if newCaCrtData == nil { 164 return fmt.Errorf("kubeconfig in %s of %s has nil clusters[0].cluster.certificate-authority-data", aocnst.MCAgentSecret, managedCluster.Name) 165 } 166 newCaCrt, err := base64.StdEncoding.DecodeString(newCaCrtData.(string)) 167 if err != nil { 168 return fmt.Errorf("could not decode certificate-authority-data in the kubeconfig in %s: %v", aocnst.MCAgentSecret, err) 169 } 170 if admCaCrt == string(newCaCrt) { 171 pkg.Log(pkg.Info, fmt.Sprintf("%v of %v took %v updated", aocnst.MCAgentSecret, managedCluster.Name, time.Since(start))) 172 return nil 173 } 174 err = fmt.Errorf("%v of %v is not updated", aocnst.MCAgentSecret, managedCluster.Name) 175 pkg.Log(pkg.Error, err.Error()) 176 return err 177 }).WithTimeout(waitTimeout).WithPolling(pollingInterval).Should(gomega.Not(gomega.HaveOccurred())) 178 } 179 180 func verifyManagedClusterRegistration(managedCluster *multicluster.Cluster, admCaCrt, cakey string) { 181 start := time.Now() 182 gomega.Eventually(func() bool { 183 newCaCrt := managedCluster. 184 GetSecretDataAsString(constants.VerrazzanoSystemNamespace, aocnst.MCRegistrationSecret, cakey) 185 if newCaCrt == admCaCrt { 186 pkg.Log(pkg.Error, fmt.Sprintf("%v of %v took %v updated", aocnst.MCRegistrationSecret, managedCluster.Name, time.Since(start))) 187 } else { 188 pkg.Log(pkg.Error, fmt.Sprintf("%v of %v is not updated", aocnst.MCRegistrationSecret, managedCluster.Name)) 189 } 190 return admCaCrt == newCaCrt 191 }).WithTimeout(waitTimeout).WithPolling(pollingInterval).Should(gomega.BeTrue(), fmt.Sprintf("Sync CA %v", managedCluster.Name)) 192 } 193 194 func verifyManagedFluentd(since time.Time) { 195 for _, managedCluster := range managedClusters { 196 gomega.Eventually(func() bool { 197 logs := managedCluster.FluentdLogs(5, since) 198 ok := checkFluentdLogs(logs) 199 if !ok { 200 pkg.Log(pkg.Error, fmt.Sprintf("%v Fluentd is not ready: \n%v\n", managedCluster.Name, logs)) 201 } 202 return ok 203 }).WithTimeout(waitTimeout).WithPolling(pollingInterval).Should(gomega.BeTrue(), fmt.Sprintf("scrape target of %s is not ready", managedCluster.Name)) 204 } 205 } 206 207 func checkFluentdLogs(logs string) bool { 208 return !strings.Contains(strings.ToUpper(logs), "ERROR") && !strings.Contains(logs, "Exception") 209 } 210 211 func verifyRegistration() { 212 for _, managedCluster := range managedClusters { 213 reg, _ := adminCluster.GetRegistration(managedCluster.Name) 214 if reg == nil { 215 adminCluster.Register(managedCluster) 216 gomega.Eventually(func() bool { 217 reg, err := adminCluster.GetRegistration(managedCluster.Name) 218 return reg != nil && err == nil 219 }).WithTimeout(waitTimeout).WithPolling(pollingInterval).Should(gomega.BeTrue(), fmt.Sprintf("%s is not registered", managedCluster.Name)) 220 } 221 } 222 } 223 224 // reapplyManagedClusterRegManifest reapplies the registration manifest on managed clusters. For 225 // self-signed certs, this manual step is expected of users so that the admin kubeconfig used by the 226 // managed clusters can be reliably updated to use the new CA cert. Otherwise intermittent timing 227 // related failures may occur 228 func reapplyManagedClusterRegManifest(newCACert string) { 229 for _, managedCluster := range managedClusters { 230 waitForManifestSecretUpdated(managedCluster.Name, newCACert) 231 gomega.Eventually(func() error { 232 reg, err := adminCluster.GetManifest(managedCluster.Name) 233 if err != nil { 234 pkg.Log(pkg.Error, fmt.Sprintf("could not get manifest for managed cluster %v error: %v", managedCluster.Name, err)) 235 return err 236 } 237 pkg.Log(pkg.Info, fmt.Sprintf("Reapplying registration manifest for managed cluster %s", managedCluster.Name)) 238 managedCluster.Apply(reg) 239 return nil 240 }).WithTimeout(waitTimeout).WithPolling(pollingInterval).Should(gomega.Not(gomega.HaveOccurred()), fmt.Sprintf("Reapply registration manifest failed for cluster %s", managedCluster.Name)) 241 } 242 } 243 244 func waitForManifestSecretUpdated(managedClusterName string, newCACert string) { 245 start := time.Now() 246 manifestSecretName := vmc.GetManifestSecretName(managedClusterName) 247 gomega.Eventually(func() error { 248 manifestBytes, err := adminCluster.GetManifest(managedClusterName) 249 if err != nil { 250 pkg.Log(pkg.Error, fmt.Sprintf("Could not get manifest secret for managed cluster %s: %v", managedClusterName, err)) 251 return err 252 } 253 resourceContainer, err := extractResourceFromManifestYAML(manifestBytes, aocnst.MCAgentSecret) 254 if err != nil { 255 return err 256 } 257 258 encodedKubeconfigData := resourceContainer.Search("data", mcconstants.KubeconfigKey) 259 if encodedKubeconfigData == nil { 260 return fmt.Errorf("could not find admin kubeconfig data in manifest") 261 } 262 updated := kubeconfigContainsCACert(encodedKubeconfigData, newCACert) 263 if updated { 264 pkg.Log(pkg.Info, fmt.Sprintf("%s took %v updated", manifestSecretName, time.Since(start))) 265 return nil 266 } 267 pkg.Log(pkg.Info, fmt.Sprintf("%s not updated", manifestSecretName)) 268 return fmt.Errorf("manifest secret for cluster %s not updated with new CA cert", managedClusterName) 269 }).WithTimeout(waitTimeout).WithPolling(pollingInterval). 270 Should(gomega.Not(gomega.HaveOccurred())) 271 } 272 273 func extractResourceFromManifestYAML(manifestBytes []byte, resourceName string) (*gabs.Container, error) { 274 yamlDocs := bytes.Split(manifestBytes, []byte("---\n")) 275 for _, yamlDoc := range yamlDocs { 276 jsonDoc, err := yaml.YAMLToJSON(yamlDoc) 277 if err != nil { 278 pkg.Log(pkg.Error, fmt.Sprintf("Could not parse YAML in manifest to JSON %v", err)) 279 return nil, err 280 } 281 resourceContainer, err := gabs.ParseJSON(jsonDoc) 282 if err != nil { 283 pkg.Log(pkg.Error, fmt.Sprintf("Could not parse JSON manifest secret: %v", err)) 284 return nil, err 285 } 286 if resourceContainer.Search("metadata", "name").Data() == resourceName { 287 return resourceContainer, nil 288 } 289 } 290 return nil, fmt.Errorf("resource %s not found in manifest", resourceName) 291 } 292 293 func kubeconfigContainsCACert(encodedKubeconfigData *gabs.Container, newCACert string) bool { 294 kubeconfig, err := base64.StdEncoding.DecodeString(encodedKubeconfigData.Data().(string)) 295 if err != nil { 296 pkg.Log(pkg.Error, fmt.Sprintf("Could not base64 decode kubeconfig in manifest: %v", err)) 297 return false 298 } 299 encodedCACert := base64.StdEncoding.EncodeToString([]byte(newCACert)) 300 return strings.Contains(string(kubeconfig), encodedCACert) 301 }