github.com/verrazzano/verrazzano@v1.7.1/cluster-operator/controllers/vmc/sync_manifest_secret.go (about)

     1  // Copyright (c) 2021, 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 vmc
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	internalcapi "github.com/verrazzano/verrazzano/cluster-operator/internal/capi"
    10  	constants3 "github.com/verrazzano/verrazzano/pkg/constants"
    11  	"k8s.io/apimachinery/pkg/api/errors"
    12  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    13  	ctrl "sigs.k8s.io/controller-runtime"
    14  	"sigs.k8s.io/controller-runtime/pkg/client"
    15  	"strings"
    16  
    17  	clusterapi "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    18  	"github.com/verrazzano/verrazzano/cluster-operator/controllers/rancher"
    19  	constants2 "github.com/verrazzano/verrazzano/pkg/mcconstants"
    20  	"github.com/verrazzano/verrazzano/pkg/rancherutil"
    21  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    22  	corev1 "k8s.io/api/core/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    26  	"sigs.k8s.io/yaml"
    27  )
    28  
    29  const (
    30  	yamlSep = "---\n"
    31  
    32  	caCertSecretKey = "cacrt"
    33  )
    34  
    35  // Create or update a secret that contains Kubernetes resource YAML which will be applied
    36  // on the managed cluster to create resources needed there. The YAML has multiple Kubernetes
    37  // resources separated by 3 hyphens ( --- ), so that applying the YAML will create/update multiple
    38  // resources at once.  This YAML is stored in the Verrazzano manifest secret.
    39  // This function returns a boolean and an error, where the boolean is true if the VMC was created by
    40  // Verrazzano and the Rancher cluster ID has not yet been set in the status. This allows the
    41  // calling function to requeue and process the VMC again before marking the VMC ready.
    42  func (r *VerrazzanoManagedClusterReconciler) syncManifestSecret(ctx context.Context, rancherEnabled bool, vmc *clusterapi.VerrazzanoManagedCluster) (bool, error) {
    43  	// Builder used to build up the full YAML
    44  	// For each secret, generate the YAML and append to the full YAML which contains multiple resources
    45  	var sb = strings.Builder{}
    46  
    47  	// if we created the VMC from a Rancher cluster, then wait for the cluster id to be populated in the status before we
    48  	// attempt to fetch the registration manifest YAML
    49  	vzVMCWaitingForClusterID := false
    50  
    51  	clusterID := vmc.Status.RancherRegistration.ClusterID
    52  	if rancherEnabled {
    53  		if vmc.Labels != nil && vmc.Labels[rancher.CreatedByLabel] == rancher.CreatedByVerrazzano && len(clusterID) == 0 {
    54  			r.log.Progressf("Waiting for Verrazzano-created VMC named %s to have a cluster id in the status before attempting to fetch Rancher registration manifest", vmc.Name)
    55  			vzVMCWaitingForClusterID = true
    56  		} else {
    57  			// register the cluster with Rancher - the cluster will show as "pending" until the
    58  			// Rancher YAML is applied on the managed cluster
    59  			// NOTE: If this errors we log it and do not fail the reconcile
    60  			rc, err := rancherutil.NewAdminRancherConfig(r.Client, r.RancherIngressHost, r.log)
    61  			if err != nil {
    62  				msg := "Failed to create Rancher API client"
    63  				r.log.Infof("Unable to connect to Rancher API on admin cluster, manifest secret will not contain Rancher YAML: %v", err)
    64  				r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationFailed, "", msg)
    65  			} else {
    66  				var rancherYAML string
    67  				rancherYAML, clusterID, err = RegisterManagedClusterWithRancher(rc, vmc.Name, vmc.Status.RancherRegistration.ClusterID, r.log)
    68  				if err != nil {
    69  					msg := "Failed to register managed cluster with Rancher"
    70  					// Even if there was a failure, if the cluster id was retrieved and is currently empty
    71  					// on the VMC, populate it during status update
    72  					r.log.Info("Failed to register managed cluster, manifest secret will not contain Rancher YAML")
    73  					r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationFailed, clusterID, msg)
    74  				} else if len(rancherYAML) == 0 {
    75  					// we successfully called the Rancher API but for some reason the returned registration manifest YAML is empty,
    76  					// set the status on the VMC and return an error so we reconcile again
    77  					r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationFailed, clusterID, "Empty Rancher manifest YAML")
    78  					msg := fmt.Sprintf("Failed retrieving Rancher manifest, YAML is an empty string for cluster ID %s", clusterID)
    79  					r.log.Infof(msg)
    80  					return vzVMCWaitingForClusterID, r.log.ErrorNewErr(msg)
    81  				} else {
    82  					msg := fmt.Sprintf("Registration of managed cluster completed successfully for cluster %s with ID %s", vmc.Name, clusterID)
    83  					r.log.Once(msg)
    84  					if vmc.Status.RancherRegistration.Status != clusterapi.RegistrationApplied {
    85  						r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationCompleted, clusterID, msg)
    86  					}
    87  					sb.WriteString(rancherYAML)
    88  				}
    89  			}
    90  		}
    91  	}
    92  
    93  	// if registration was successful and there is a cluster ID OR there is CAPI access to cluster...
    94  	if vmc.Status.ClusterRef != nil || len(clusterID) > 0 {
    95  		// check for existence of verrazzano-system namespace
    96  		vsNamespaceExists, _ := isNamespaceCreated(vmc, r, clusterID, constants.VerrazzanoSystemNamespace)
    97  
    98  		if vsNamespaceExists {
    99  			// add agent secret YAML
   100  			agentYaml, err := r.getSecretAsYaml(GetAgentSecretName(vmc.Name), vmc.Namespace,
   101  				constants.MCAgentSecret, constants.VerrazzanoSystemNamespace)
   102  			if err != nil {
   103  				return false, err
   104  			}
   105  			sb.Write([]byte(yamlSep))
   106  			sb.Write(agentYaml)
   107  
   108  			// add registration secret YAML
   109  			regYaml, err := r.getSecretAsYaml(GetRegistrationSecretName(vmc.Name), vmc.Namespace,
   110  				constants.MCRegistrationSecret, constants.VerrazzanoSystemNamespace)
   111  			if err != nil {
   112  				return false, err
   113  			}
   114  			sb.Write([]byte(yamlSep))
   115  			sb.Write(regYaml)
   116  		}
   117  	}
   118  	// create/update the manifest secret with the YAML
   119  	_, err := r.createOrUpdateManifestSecret(vmc, sb.String())
   120  	if err != nil {
   121  		return vzVMCWaitingForClusterID, err
   122  	}
   123  
   124  	// finally, update the VMC
   125  	existingVMC := &clusterapi.VerrazzanoManagedCluster{}
   126  	err = r.Get(context.TODO(), types.NamespacedName{Namespace: vmc.Namespace, Name: vmc.Name}, existingVMC)
   127  	if err != nil {
   128  		r.log.Errorf("Failed to get the existing VMC %s from the cluster: %v", vmc.Name, err)
   129  	}
   130  	existingVMC.Spec.ManagedClusterManifestSecret = GetManifestSecretName(vmc.Name)
   131  
   132  	err = r.Update(ctx, existingVMC)
   133  	if err != nil {
   134  		return vzVMCWaitingForClusterID, err
   135  	}
   136  
   137  	return vzVMCWaitingForClusterID, nil
   138  }
   139  
   140  // syncCACertSecret gets the CA cert from the managed cluster (if the cluster is active) and creates
   141  // or updates the CA cert secret. If the secret is created, it also updates the VMC with the secret
   142  // name. This function returns true if the sync was completed, false if it was not needed or not
   143  // completed, and any error that occurred
   144  func (r *VerrazzanoManagedClusterReconciler) syncCACertSecret(ctx context.Context, vmc *clusterapi.VerrazzanoManagedCluster, rancherEnabled bool) (bool, error) {
   145  	caCert := ""
   146  	var err error
   147  	if rancherEnabled {
   148  		caCert, err = r.getCACertViaRancher(vmc)
   149  		if err != nil {
   150  			return false, err
   151  		}
   152  	}
   153  	if len(caCert) == 0 && vmc.Status.ClusterRef != nil {
   154  		caCert, err = r.getCACertViaCAPI(ctx, vmc)
   155  		if err != nil {
   156  			return false, err
   157  		}
   158  	}
   159  
   160  	if len(caCert) > 0 {
   161  		caSecretName := getCASecretName(vmc.Name)
   162  		r.log.Infof("Retrieved CA cert from managed cluster, creating/updating secret %s", caSecretName)
   163  		if _, err = r.createOrUpdateCASecret(vmc, caCert); err != nil {
   164  			return false, err
   165  		}
   166  		if len(caSecretName) > 0 {
   167  			vmc.Spec.CASecret = caSecretName
   168  			// update the VMC with ca secret name
   169  			r.log.Infof("Updating VMC %s with managed cluster CA secret %s", vmc.Name, caSecretName)
   170  			// Replace the VMC in the update call with a copy
   171  			// That way the existing VMC status updates do not get overwritten by the update
   172  			updateVMC := vmc.DeepCopy()
   173  			err = r.Update(context.TODO(), updateVMC)
   174  			if err != nil {
   175  				return false, err
   176  			}
   177  			return true, nil
   178  		}
   179  	}
   180  
   181  	return false, nil
   182  }
   183  
   184  func (r *VerrazzanoManagedClusterReconciler) getCACertViaCAPI(ctx context.Context, vmc *clusterapi.VerrazzanoManagedCluster) (string, error) {
   185  	caCrt := ""
   186  	cluster := &unstructured.Unstructured{}
   187  	cluster.SetGroupVersionKind(internalcapi.GVKCAPICluster)
   188  	err := r.Get(context.TODO(), types.NamespacedName{Namespace: vmc.Status.ClusterRef.Namespace, Name: vmc.Status.ClusterRef.Name}, cluster)
   189  	if err != nil && !errors.IsNotFound(err) {
   190  		return "", err
   191  	}
   192  	// register the cluster if Verrazzano installed on workload cluster
   193  	workloadClient, err := r.getWorkloadClusterClient(cluster)
   194  	if err != nil {
   195  		r.log.Errorf("Error getting workload cluster %s client: %v", cluster.GetName(), err)
   196  		return "", err
   197  	}
   198  
   199  	// ensure Verrazzano is installed and ready in workload cluster
   200  	ready, err := r.isVerrazzanoReady(ctx, workloadClient)
   201  	if err != nil {
   202  		return "", err
   203  	}
   204  	if !ready {
   205  		r.log.Progressf("Verrazzano not installed or not ready in cluster %s", cluster.GetName())
   206  		return "", err
   207  	}
   208  	// if verrazzano-tls-ca exists, the cluster is untrusted
   209  	err = workloadClient.Get(ctx, types.NamespacedName{Name: constants3.PrivateCABundle,
   210  		Namespace: constants.VerrazzanoSystemNamespace}, &corev1.Secret{})
   211  	if err != nil {
   212  		if errors.IsNotFound(err) {
   213  			// cluster is trusted
   214  			r.log.Infof("Cluster %s is using trusted certs", cluster.GetName())
   215  		} else {
   216  			// unexpected error
   217  			return "", err
   218  		}
   219  	} else {
   220  		// need to create a CA secret in admin cluster
   221  		// get the workload cluster API CA cert
   222  		caCrt, err = r.getWorkloadClusterCACert(workloadClient)
   223  		if err != nil {
   224  			return "", err
   225  		}
   226  		// persist the workload API certificate on the admin cluster
   227  		adminWorkloadCertSecret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
   228  			Name:      fmt.Sprintf("ca-secret-%s", cluster.GetName()),
   229  			Namespace: constants.VerrazzanoMultiClusterNamespace}}
   230  		if _, err := ctrl.CreateOrUpdate(context.TODO(), r.Client, adminWorkloadCertSecret, func() error {
   231  			if len(adminWorkloadCertSecret.Data) == 0 {
   232  				adminWorkloadCertSecret.Data = make(map[string][]byte)
   233  			}
   234  			adminWorkloadCertSecret.Data["cacrt"] = []byte(caCrt)
   235  
   236  			return nil
   237  		}); err != nil {
   238  			return "", err
   239  		}
   240  	}
   241  
   242  	return caCrt, nil
   243  }
   244  
   245  func (r *VerrazzanoManagedClusterReconciler) getCACertViaRancher(vmc *clusterapi.VerrazzanoManagedCluster) (string, error) {
   246  	clusterID := vmc.Status.RancherRegistration.ClusterID
   247  	if len(clusterID) == 0 {
   248  		return "", nil
   249  	}
   250  	if len(vmc.Spec.CASecret) > 0 {
   251  		return "", nil
   252  	}
   253  	rc, err := rancherutil.NewAdminRancherConfig(r.Client, r.RancherIngressHost, r.log)
   254  	if err != nil {
   255  		return "", err
   256  	}
   257  	if rc == nil {
   258  		return "", nil
   259  	}
   260  
   261  	isActive, err := isManagedClusterActiveInRancher(rc, clusterID, r.log)
   262  	if err != nil {
   263  		return "", err
   264  	}
   265  	if !isActive {
   266  		r.log.Progressf("Waiting for managed cluster with id %s to become active before fetching CA cert", clusterID)
   267  		return "", nil
   268  	}
   269  
   270  	caCert, err := getCACertFromManagedCluster(rc, clusterID, r.log)
   271  	if err != nil {
   272  		return "", err
   273  	}
   274  
   275  	return caCert, nil
   276  }
   277  
   278  // Update the Rancher registration status
   279  func (r *VerrazzanoManagedClusterReconciler) updateRancherStatus(ctx context.Context, vmc *clusterapi.VerrazzanoManagedCluster, status clusterapi.RancherRegistrationStatus, rancherClusterID string, message string) {
   280  	// Skip the update if the status has not changed
   281  	if vmc.Status.RancherRegistration.Status == status &&
   282  		vmc.Status.RancherRegistration.Message == message &&
   283  		vmc.Status.RancherRegistration.ClusterID == rancherClusterID {
   284  		return
   285  	}
   286  	vmc.Status.RancherRegistration.Status = status
   287  	// don't wipe out existing cluster id with empty string
   288  	if rancherClusterID != "" {
   289  		vmc.Status.RancherRegistration.ClusterID = rancherClusterID
   290  	}
   291  	vmc.Status.RancherRegistration.Message = message
   292  
   293  	// Fetch the existing VMC to avoid conflicts in the status update
   294  	existingVMC := &clusterapi.VerrazzanoManagedCluster{}
   295  	err := r.Get(context.TODO(), types.NamespacedName{Namespace: vmc.Namespace, Name: vmc.Name}, existingVMC)
   296  	if err != nil {
   297  		r.log.Errorf("Failed to get the existing VMC %s from the cluster: %v", vmc.Name, err)
   298  	}
   299  	existingVMC.Status.RancherRegistration = vmc.Status.RancherRegistration
   300  
   301  	err = r.Status().Update(ctx, existingVMC)
   302  	if err != nil {
   303  		r.log.Errorf("Failed to update Rancher registration status for VMC %s: %v", vmc.Name, err)
   304  	}
   305  }
   306  
   307  // Create or update the manifest secret
   308  func (r *VerrazzanoManagedClusterReconciler) createOrUpdateManifestSecret(vmc *clusterapi.VerrazzanoManagedCluster, yamlData string) (controllerutil.OperationResult, error) {
   309  	var secret corev1.Secret
   310  	secret.Namespace = vmc.Namespace
   311  	secret.Name = GetManifestSecretName(vmc.Name)
   312  
   313  	return controllerutil.CreateOrUpdate(context.TODO(), r.Client, &secret, func() error {
   314  		r.mutateManifestSecret(&secret, yamlData)
   315  		// This SetControllerReference call will trigger garbage collection i.e. the secret
   316  		// will automatically get deleted when the VerrazzanoManagedCluster is deleted
   317  		return controllerutil.SetControllerReference(vmc, &secret, r.Scheme)
   318  	})
   319  }
   320  
   321  // Mutate the secret, setting the yaml data
   322  func (r *VerrazzanoManagedClusterReconciler) mutateManifestSecret(secret *corev1.Secret, yamlData string) {
   323  	secret.Type = corev1.SecretTypeOpaque
   324  	secret.Data = map[string][]byte{
   325  		constants2.YamlKey: []byte(yamlData),
   326  	}
   327  }
   328  
   329  // createOrUpdateCASecret creates or updates the secret containing the managed cluster CA cert
   330  func (r *VerrazzanoManagedClusterReconciler) createOrUpdateCASecret(vmc *clusterapi.VerrazzanoManagedCluster, caCert string) (controllerutil.OperationResult, error) {
   331  	var secret corev1.Secret
   332  	secret.Namespace = vmc.Namespace
   333  	secret.Name = getCASecretName(vmc.Name)
   334  
   335  	return controllerutil.CreateOrUpdate(context.TODO(), r.Client, &secret, func() error {
   336  		r.mutateCASecret(&secret, caCert)
   337  		// This SetControllerReference call will trigger garbage collection i.e. the secret
   338  		// will automatically get deleted when the VerrazzanoManagedCluster is deleted
   339  		return controllerutil.SetControllerReference(vmc, &secret, r.Scheme)
   340  	})
   341  }
   342  
   343  // mutateCASecret mutates the CA secret, setting the CA cert data
   344  func (r *VerrazzanoManagedClusterReconciler) mutateCASecret(secret *corev1.Secret, caCert string) {
   345  	secret.Type = corev1.SecretTypeOpaque
   346  	secret.Data = map[string][]byte{
   347  		caCertSecretKey: []byte(caCert),
   348  	}
   349  }
   350  
   351  // Get the specified secret then convert to YAML.
   352  func (r *VerrazzanoManagedClusterReconciler) getSecretAsYaml(name string, namespace string, targetName string, targetNamespace string) (yamlData []byte, err error) {
   353  	var secret corev1.Secret
   354  	secretNsn := types.NamespacedName{
   355  		Namespace: namespace,
   356  		Name:      name,
   357  	}
   358  	if err := r.Get(context.TODO(), secretNsn, &secret); err != nil {
   359  		return []byte(""), fmt.Errorf("Failed to fetch the service account secret %s/%s, %v", namespace, name, err)
   360  	}
   361  	// Create a new ObjectMeta with target name and namespace
   362  	secret.ObjectMeta = metav1.ObjectMeta{
   363  		Namespace: targetNamespace,
   364  		Name:      targetName,
   365  	}
   366  	yamlData, err = yaml.Marshal(secret)
   367  	return yamlData, err
   368  }
   369  
   370  // isVerrazzanoReady checks to see whether the Verrazzano resource on the workload cluster is ready
   371  func (r *VerrazzanoManagedClusterReconciler) isVerrazzanoReady(ctx context.Context, workloadClient client.Client) (bool, error) {
   372  	if err := workloadClient.Get(ctx,
   373  		types.NamespacedName{Name: constants.Verrazzano, Namespace: constants.VerrazzanoSystemNamespace},
   374  		&corev1.Secret{}); err != nil {
   375  		if !errors.IsNotFound(err) {
   376  			r.log.Debugf("Failed to retrieve verrazzano secret: %v", err)
   377  			return false, err
   378  		}
   379  		r.log.Debugf("Verrazzano secret not found")
   380  		return false, nil
   381  	}
   382  	return true, nil
   383  }
   384  
   385  // getWorkloadClusterCACert retrieves the API endpoint CA certificate from the workload cluster
   386  func (r *VerrazzanoManagedClusterReconciler) getWorkloadClusterCACert(workloadClient client.Client) (string, error) {
   387  	caCrtSecret := &corev1.Secret{}
   388  	err := workloadClient.Get(context.TODO(), types.NamespacedName{
   389  		Name:      constants3.VerrazzanoIngressTLSSecret,
   390  		Namespace: constants.VerrazzanoSystemNamespace},
   391  		caCrtSecret)
   392  	if err != nil {
   393  		return "", err
   394  	}
   395  	caCrt, ok := caCrtSecret.Data["ca.crt"]
   396  	if !ok {
   397  		return "", fmt.Errorf("Workload cluster CA certificate not found in verrazzano-tls secret")
   398  	}
   399  	return string(caCrt), nil
   400  }
   401  
   402  // GetManifestSecretName returns the manifest secret name
   403  func GetManifestSecretName(vmcName string) string {
   404  	const manifestSecretSuffix = "-manifest"
   405  	return generateManagedResourceName(vmcName) + manifestSecretSuffix
   406  }
   407  
   408  // getCASecretName returns the CA secret name
   409  func getCASecretName(vmcName string) string {
   410  	return "ca-secret-" + vmcName
   411  }