github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/vmc/sync_registration_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  	"strings"
    10  
    11  	clusterapi "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    12  	"github.com/verrazzano/verrazzano/pkg/constants"
    13  	"github.com/verrazzano/verrazzano/pkg/mcconstants"
    14  	"github.com/verrazzano/verrazzano/pkg/vzcr"
    15  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    16  	corev1 "k8s.io/api/core/v1"
    17  	k8net "k8s.io/api/networking/v1"
    18  	"k8s.io/apimachinery/pkg/api/errors"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    22  )
    23  
    24  const operatorOSIngress = "opensearch"
    25  const defaultSecretName = "verrazzano"
    26  
    27  // Create a registration secret with the managed cluster information.  This secret will
    28  // be used on the managed cluster to get information about itself, like the cluster name
    29  func (r *VerrazzanoManagedClusterReconciler) syncRegistrationSecret(vmc *clusterapi.VerrazzanoManagedCluster) error {
    30  	secretName := GetRegistrationSecretName(vmc.Name)
    31  	managedNamespace := vmc.Namespace
    32  
    33  	_, err := r.createOrUpdateRegistrationSecret(vmc, secretName, managedNamespace)
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	return nil
    39  }
    40  
    41  // Create or update the registration secret
    42  func (r *VerrazzanoManagedClusterReconciler) createOrUpdateRegistrationSecret(vmc *clusterapi.VerrazzanoManagedCluster, name string, namespace string) (controllerutil.OperationResult, error) {
    43  	var secret corev1.Secret
    44  	secret.Namespace = namespace
    45  	secret.Name = name
    46  
    47  	return controllerutil.CreateOrUpdate(context.TODO(), r.Client, &secret, func() error {
    48  		err := r.mutateRegistrationSecret(&secret, vmc.Name)
    49  		if err != nil {
    50  			return err
    51  		}
    52  		// This SetControllerReference call will trigger garbage collection i.e. the secret
    53  		// will automatically get deleted when the VerrazzanoManagedCluster is deleted
    54  		return controllerutil.SetControllerReference(vmc, &secret, r.Scheme)
    55  	})
    56  }
    57  
    58  // Mutate the secret, setting the kubeconfig data
    59  func (r *VerrazzanoManagedClusterReconciler) mutateRegistrationSecret(secret *corev1.Secret, manageClusterName string) error {
    60  	secret.Type = corev1.SecretTypeOpaque
    61  
    62  	vzList := vzapi.VerrazzanoList{}
    63  	err := r.List(context.TODO(), &vzList, &client.ListOptions{})
    64  	if err != nil {
    65  		r.log.Errorf("Failed to list Verrazzano CR: %v", err)
    66  		return err
    67  	}
    68  	if len(vzList.Items) < 1 {
    69  		return fmt.Errorf("can not find Verrazzano CR")
    70  	}
    71  
    72  	// Get the fluentd configuration for ES URL and secret
    73  	fluentdESURL, fluentdESSecretName, err := r.getVzESURLSecret(&vzList)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	// Decide which ES URL to use.
    79  	// If the fluentd OPENSEARCH_URL is the default "http://verrazzano-authproxy-opensearch:8775", use VMI ES ingress URL.
    80  	// If the fluentd OPENSEARCH_URL is not the default, meaning it is a custom ES, use the external ES URL.
    81  	esURL := fluentdESURL
    82  	if esURL == constants.DefaultOpensearchURL {
    83  		esURL, err = r.getESURL(vzList)
    84  		if err != nil {
    85  			return err
    86  		}
    87  	}
    88  
    89  	// Get the CA bundle needed to connect to the admin keycloak
    90  	adminCaBundle, err := r.getAdminCaBundle()
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	// Decide which ES secret to use for username/password and password.
    96  	// If the fluentd opensearchSecret is the default "verrazzano", use VerrazzanoESInternal secret for username/password, and adminCaBundle for ES CA bundle.
    97  	// if the fluentd opensearchSecret is not the default, meaning it is a custom secret, use its username/password and CA bundle.
    98  	var esCaBundle []byte
    99  	var esUsername []byte
   100  	var esPassword []byte
   101  	if fluentdESSecretName != "verrazzano" {
   102  		esSecret, err := r.getSecret(constants.VerrazzanoSystemNamespace, fluentdESSecretName, true)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		esCaBundle = esSecret.Data[mcconstants.FluentdESCaBundleKey]
   107  		esUsername = esSecret.Data[mcconstants.VerrazzanoUsernameKey]
   108  		esPassword = esSecret.Data[mcconstants.VerrazzanoPasswordKey]
   109  	} else {
   110  		esSecret, err := r.getSecret(constants.VerrazzanoSystemNamespace, constants.VerrazzanoESInternal, false)
   111  		if err != nil && !errors.IsNotFound(err) {
   112  			return err
   113  		}
   114  
   115  		if len(esSecret.Data) > 0 {
   116  			esCaBundle = adminCaBundle
   117  			esUsername = esSecret.Data[mcconstants.VerrazzanoUsernameKey]
   118  			esPassword = esSecret.Data[mcconstants.VerrazzanoPasswordKey]
   119  		}
   120  
   121  	}
   122  
   123  	// Get the keycloak URL
   124  	keycloakURL, err := r.getIngressURL("keycloak", "keycloak")
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	dexURL, err := r.getIngressURL("verrazzano-auth", "dex")
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	if keycloakURL == "" && dexURL == "" {
   135  		return fmt.Errorf("no oidc provider found")
   136  	}
   137  
   138  	oidcProvider := "keycloak"
   139  	if vzcr.IsDexEnabled(&vzList.Items[0]) {
   140  		oidcProvider = "dex"
   141  	}
   142  
   143  	// Get the Jaeger OpenSearch related data if it exists
   144  	jaegerStorage, err := r.getJaegerOpenSearchConfig(&vzList)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	// Build the secret data
   150  	secret.Data = map[string][]byte{
   151  		mcconstants.ManagedClusterNameKey:   []byte(manageClusterName),
   152  		mcconstants.ESURLKey:                []byte(esURL),
   153  		mcconstants.ESCaBundleKey:           esCaBundle,
   154  		mcconstants.RegistrationUsernameKey: esUsername,
   155  		mcconstants.RegistrationPasswordKey: esPassword,
   156  		mcconstants.KeycloakURLKey:          []byte(keycloakURL),
   157  		mcconstants.AdminCaBundleKey:        adminCaBundle,
   158  		mcconstants.JaegerOSURLKey:          []byte(jaegerStorage.URL),
   159  		mcconstants.JaegerOSTLSCAKey:        jaegerStorage.CA,
   160  		mcconstants.JaegerOSTLSKey:          jaegerStorage.TLSKey,
   161  		mcconstants.JaegerOSTLSCertKey:      jaegerStorage.TLSCert,
   162  		mcconstants.JaegerOSUsernameKey:     jaegerStorage.username,
   163  		mcconstants.JaegerOSPasswordKey:     jaegerStorage.password,
   164  		mcconstants.DexURLKey:               []byte(dexURL),
   165  		mcconstants.OidcProviderKey:         []byte(oidcProvider),
   166  	}
   167  	return nil
   168  }
   169  
   170  // GetRegistrationSecretName returns the registration secret name
   171  func GetRegistrationSecretName(vmcName string) string {
   172  	const registrationSecretSuffix = "-registration"
   173  	return generateManagedResourceName(vmcName) + registrationSecretSuffix
   174  }
   175  
   176  // getVzESURLSecret returns the opensearchURL and opensearchSecret from Verrazzano CR
   177  func (r *VerrazzanoManagedClusterReconciler) getVzESURLSecret(vzList *vzapi.VerrazzanoList) (string, string, error) {
   178  	url := constants.DefaultOpensearchURL
   179  	secret := defaultSecretName
   180  	// what to do when there is more than one Verrazzano CR
   181  	for _, vz := range vzList.Items {
   182  		if vz.Spec.Components.Fluentd != nil {
   183  			if len(vz.Spec.Components.Fluentd.OpenSearchURL) > 0 {
   184  				url = vz.Spec.Components.Fluentd.OpenSearchURL
   185  			}
   186  			if len(vz.Spec.Components.Fluentd.OpenSearchSecret) > 0 {
   187  				secret = vz.Spec.Components.Fluentd.OpenSearchSecret
   188  			}
   189  		}
   190  	}
   191  	return url, secret, nil
   192  }
   193  
   194  // Get the opensearch URL.
   195  func (r *VerrazzanoManagedClusterReconciler) getESURL(vzList vzapi.VerrazzanoList) (URL string, err error) {
   196  	if len(vzList.Items) == 0 {
   197  		return "", nil
   198  	}
   199  	if !vzcr.IsOpenSearchEnabled(&vzList.Items[0]) {
   200  		return "", nil
   201  	}
   202  	var Ingress k8net.Ingress
   203  	nsn := types.NamespacedName{
   204  		Namespace: constants.VerrazzanoSystemNamespace,
   205  		Name:      operatorOSIngress,
   206  	}
   207  	if err := r.Get(context.TODO(), nsn, &Ingress); err != nil {
   208  		return "", fmt.Errorf("failed to fetch the OpenSearch ingress %s/%s, %v", nsn.Namespace, nsn.Name, err)
   209  	}
   210  	if len(Ingress.Spec.Rules) == 0 {
   211  		return "", fmt.Errorf("OpenSearch ingress %s/%s missing host entry in rule", nsn.Namespace, nsn.Name)
   212  	}
   213  	host := Ingress.Spec.Rules[0].Host
   214  	if len(Ingress.Spec.Rules) == 0 {
   215  		return "", fmt.Errorf("OpenSearch ingress %s/%s host field is empty", nsn.Namespace, nsn.Name)
   216  	}
   217  	return fmt.Sprintf("https://%s:443", host), nil
   218  }
   219  
   220  // Get secret from verrazzano-system namespace
   221  func (r *VerrazzanoManagedClusterReconciler) getSecret(namespace string, secretName string, required bool) (corev1.Secret, error) {
   222  	var secret corev1.Secret
   223  	nsn := types.NamespacedName{
   224  		Namespace: namespace,
   225  		Name:      secretName,
   226  	}
   227  	err := r.Get(context.TODO(), nsn, &secret)
   228  	if err != nil {
   229  		if !required && errors.IsNotFound(err) {
   230  			return corev1.Secret{}, err
   231  		}
   232  		return corev1.Secret{}, fmt.Errorf("failed to fetch the secret %s/%s, %v", nsn.Namespace, nsn.Name, err)
   233  	}
   234  	return secret, nil
   235  }
   236  
   237  // Get the CA bundle used by verrazzano ingress and the optional rancher-ca-additional secret
   238  func (r *VerrazzanoManagedClusterReconciler) getAdminCaBundle() ([]byte, error) {
   239  	var caBundle []byte
   240  
   241  	// Append the CA bundle from verrazzano-tls-ca secret if it exists
   242  	optSecret, err := r.getSecret(constants.VerrazzanoSystemNamespace, constants.PrivateCABundle, false)
   243  	if err != nil && !errors.IsNotFound(err) {
   244  		return nil, err
   245  	}
   246  	if err == nil {
   247  		// Combine the two CA bundles
   248  		tlsCABundle := optSecret.Data[constants.RancherTLSCAKey]
   249  		if len(tlsCABundle) > 0 {
   250  			r.log.Debugf("Adding tls-ca bundle to Admin CA bundle")
   251  			caBundle = tlsCABundle
   252  		}
   253  	}
   254  
   255  	// Append the CA bundle from the ingress vzIngressSecret if it exists and is not already present in the bundle
   256  	vzIngressSecret, err := r.getSecret(constants.VerrazzanoSystemNamespace, constants.VerrazzanoIngressTLSSecret, true)
   257  	if err != nil && !errors.IsNotFound(err) {
   258  		return nil, err
   259  	}
   260  	tlsIngressCA, found := vzIngressSecret.Data[mcconstants.CaCrtKey]
   261  	if found {
   262  		if !strings.Contains(strings.TrimSpace(string(caBundle)), strings.TrimSpace(string(tlsIngressCA))) {
   263  			r.log.Infof("Adding ingress CA cert bundle to Admin CA bundle")
   264  			caBundle = append(caBundle, tlsIngressCA...)
   265  		} else {
   266  			r.log.Debugf("ingress CA bundle already present in bundle data, skipping")
   267  		}
   268  	}
   269  
   270  	return caBundle, nil
   271  }
   272  
   273  func (r *VerrazzanoManagedClusterReconciler) getIngressURL(namespace string, name string) (string, error) {
   274  	var ingress = &k8net.Ingress{}
   275  	err := r.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, ingress)
   276  	if err != nil && errors.IsNotFound(err) {
   277  		r.log.Infof("ingress %s/%s does not exist", namespace, name)
   278  		return "", nil
   279  	}
   280  
   281  	if err != nil {
   282  		return "", fmt.Errorf("unable to fetch ingress %s/%s, %v", namespace, name, err)
   283  	}
   284  
   285  	r.log.Infof("found ingress %s/%s", namespace, name)
   286  	return fmt.Sprintf("%s://%s", "https", ingress.Spec.Rules[0].Host), nil
   287  }