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 }