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  }