github.com/verrazzano/verrazzano@v1.7.0/platform-operator/controllers/secrets/cloud_credential.go (about)

     1  // Copyright (c) 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 secrets
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/rancher"
    11  	"go.uber.org/zap"
    12  	corev1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    15  	"k8s.io/apimachinery/pkg/runtime/schema"
    16  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    17  	"strings"
    18  )
    19  
    20  const (
    21  	rancherCcTenancyField     = "ocicredentialConfig-tenancyId"
    22  	rancherCcUserField        = "ocicredentialConfig-userId"
    23  	rancherCcFingerprintField = "ocicredentialConfig-fingerprint"
    24  	rancherCcRegionField      = "ocicredentialConfig-region"
    25  	rancherCcPassphraseField  = "ocicredentialConfig-passphrase" //nolint:gosec //#gosec G101
    26  	rancherCcKeyField         = "ocicredentialConfig-privateKeyContents"
    27  	ociCapiTenancyField       = "tenancy"
    28  	ociCapiUserField          = "user"
    29  	ociCapiFingerprintField   = "fingerprint"
    30  	ociCapiRegionField        = "region"
    31  	ociCapiPassphraseField    = "passphrase" //nolint:gosec //#gosec G101
    32  	ociCapiKeyField           = "key"
    33  )
    34  
    35  type rancherMgmtCluster struct {
    36  	Metadata struct {
    37  		Name string `json:"name"`
    38  	} `json:"metadata"`
    39  	Spec struct {
    40  		GenericEngineConfig struct {
    41  			CloudCredentialID string `json:"cloudCredentialId"`
    42  		} `json:"genericEngineConfig"`
    43  	} `json:"spec"`
    44  }
    45  
    46  // createOrUpdateCAPISecret updates CAPI secret based on the updated credentials
    47  func (r *VerrazzanoSecretsReconciler) updateCAPISecret(updatedSecret *corev1.Secret, clusterCredential *corev1.Secret) error {
    48  	data := map[string][]byte{
    49  		ociCapiTenancyField:     updatedSecret.Data[rancherCcTenancyField],
    50  		ociCapiUserField:        updatedSecret.Data[rancherCcUserField],
    51  		ociCapiFingerprintField: updatedSecret.Data[rancherCcFingerprintField],
    52  		ociCapiRegionField:      clusterCredential.Data[ociCapiRegionField],
    53  		ociCapiPassphraseField:  updatedSecret.Data[rancherCcPassphraseField],
    54  		ociCapiKeyField:         updatedSecret.Data[rancherCcKeyField],
    55  	}
    56  	clusterCredential.Data = data
    57  	err := r.Client.Update(context.TODO(), clusterCredential)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	return nil
    62  }
    63  
    64  // checkClusterCredentials checks whether the updated credential is being used by any OCNE cluster. If a cluster is using that credential, the OCNE cluster's copy of the credential also gets updated
    65  func (r *VerrazzanoSecretsReconciler) checkClusterCredentials(updatedSecret *corev1.Secret) error {
    66  	ocneClustersList, err := r.getOCNEClustersList()
    67  	if err != nil {
    68  		zap.S().Errorf("Failed to get OCNE Cluster list")
    69  		return err
    70  	}
    71  	for _, cluster := range ocneClustersList.Items {
    72  		var rancherMgmtCluster rancherMgmtCluster
    73  		clusterJSON, err := cluster.MarshalJSON()
    74  		if err != nil {
    75  			return err
    76  		}
    77  		if err = json.Unmarshal(clusterJSON, &rancherMgmtCluster); err != nil {
    78  			return err
    79  		}
    80  		// if the cluster is an OCNE cluster
    81  		if rancherMgmtCluster.Spec.GenericEngineConfig.CloudCredentialID != "" {
    82  			// extract cloud credential name from CloudCredentialID field
    83  			capiCredential := strings.Split(rancherMgmtCluster.Spec.GenericEngineConfig.CloudCredentialID, ":")
    84  			// if cloud credential name matches updatedSecret name, get and update the cc copy held by the cluster
    85  			if len(capiCredential) >= 2 {
    86  				if capiCredential[1] == updatedSecret.Name {
    87  					secretName := fmt.Sprintf("%s-principal", rancherMgmtCluster.Metadata.Name)
    88  					clusterCredential := &corev1.Secret{
    89  						ObjectMeta: metav1.ObjectMeta{
    90  							Name:      secretName,
    91  							Namespace: rancherMgmtCluster.Metadata.Name,
    92  						},
    93  					}
    94  					// update cluster's cloud credential copy
    95  					_, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, clusterCredential, func() error {
    96  						r.updateCAPISecret(updatedSecret, clusterCredential)
    97  						return nil
    98  					})
    99  					if err != nil {
   100  						zap.S().Errorf("Failed to update CAPI secret")
   101  						return err
   102  					}
   103  				}
   104  			}
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  // getOCNEClustersList returns the list of OCNE clusters
   111  func (r *VerrazzanoSecretsReconciler) getOCNEClustersList() (*unstructured.UnstructuredList, error) {
   112  	var ocneClustersList *unstructured.UnstructuredList
   113  	gvr := GetOCNEClusterAPIGVRForResource("clusters")
   114  	ocneClustersList, err := r.DynamicClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
   115  	if err != nil {
   116  		return nil, fmt.Errorf("failed to list %s/%s/%s: %v", gvr.Resource, gvr.Group, gvr.Version, err)
   117  	}
   118  	return ocneClustersList, nil
   119  }
   120  
   121  // GetOCNEClusterAPIGVRForResource returns a clusters.cluster.x-k8s.io GroupVersionResource structure
   122  func GetOCNEClusterAPIGVRForResource(resource string) schema.GroupVersionResource {
   123  	return schema.GroupVersionResource{
   124  		Group:    "management.cattle.io",
   125  		Version:  "v3",
   126  		Resource: resource,
   127  	}
   128  }
   129  
   130  func isOCNECloudCredential(secret *corev1.Secret) bool {
   131  	// if secret is a cloud credential in the cattle-global-data ns
   132  	if secret.Namespace == rancher.CattleGlobalDataNamespace && secret.Data["ocicredentialConfig-fingerprint"] != nil {
   133  		return true
   134  	}
   135  	// secret is not cloud credential
   136  	return false
   137  }