github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/vmo/secret.go (about)

     1  // Copyright (C) 2020, 2022, 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 vmo
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"golang.org/x/crypto/bcrypt"
    10  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    11  
    12  	"reflect"
    13  	"regexp"
    14  	"strings"
    15  
    16  	vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    17  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    18  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/secrets"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/labels"
    21  )
    22  
    23  const (
    24  	// PasswordSeparator separates passwords from hashes
    25  	PasswordSeparator = ":"
    26  	// LineSeparator separates password records
    27  	LineSeparator = "\n"
    28  )
    29  
    30  // HashedPasswords name => hash
    31  type HashedPasswords map[string]string
    32  
    33  func hashBcrypt(password string) (string, error) {
    34  	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    35  	if err != nil {
    36  		return "", err
    37  	}
    38  
    39  	return string(hashedPassword), nil
    40  }
    41  
    42  // Bytes bytes representation
    43  func (hp HashedPasswords) Bytes() (passwordBytes []byte) {
    44  	passwordBytes = []byte{}
    45  	for name, hash := range hp {
    46  		passwordBytes = append(passwordBytes, []byte(name+PasswordSeparator+hash+LineSeparator)...)
    47  	}
    48  	return passwordBytes
    49  }
    50  
    51  // SetPassword set a password for a user with a hashing algo
    52  func (hp HashedPasswords) SetPassword(name, password string) (err error) {
    53  	hashValue, err := hashBcrypt(password)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	hp[name] = hashValue
    58  	return nil
    59  }
    60  
    61  // GetAuthSecrets returns username and password
    62  func GetAuthSecrets(controller *Controller, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) (string, string, error) {
    63  	// setup username/passwords and secrets
    64  
    65  	username, err := controller.loadSecretData(vmo.Namespace,
    66  		vmo.Spec.SecretsName, constants.VMOSecretUsernameField)
    67  	if err != nil {
    68  		return "", "", controller.log.ErrorfNewErr("Failed getting username from secret %s/%s: %v", vmo.Namespace, vmo.Spec.SecretsName, err)
    69  	}
    70  
    71  	password, err := controller.loadSecretData(vmo.Namespace,
    72  		vmo.Spec.SecretsName, constants.VMOSecretPasswordField)
    73  	if err != nil {
    74  		return "", "", controller.log.ErrorfNewErr("Failed getting password from secret %s/%s: %v", vmo.Namespace, vmo.Spec.SecretsName, err)
    75  	}
    76  	return string(username), string(password), nil
    77  }
    78  
    79  func GetClusterNameFromSecret(controller *Controller, namespace string) (string, error) {
    80  	clusterName, err := controller.loadSecretData(namespace, constants.MCRegistrationSecret, constants.ClusterNameData)
    81  	if err == nil {
    82  		return string(clusterName), nil
    83  	}
    84  	if apierrors.IsNotFound(err) {
    85  		clusterName, err = controller.loadSecretData(namespace, constants.MCLocalRegistrationSecret, constants.ClusterNameData)
    86  		if err != nil {
    87  			return "", controller.log.ErrorfNewErr("Failed to load secret %s data %s: %v", constants.MCLocalRegistrationSecret, constants.ClusterNameData, err)
    88  		}
    89  		return string(clusterName), err
    90  	}
    91  	return "", err
    92  }
    93  
    94  // CreateOrUpdateAuthSecrets create/updates auth secrets
    95  func CreateOrUpdateAuthSecrets(controller *Controller, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, credsMap map[string]string) error {
    96  
    97  	passwords := HashedPasswords(map[string]string{})
    98  	for k, v := range credsMap {
    99  		if err := passwords.SetPassword(k, v); err != nil {
   100  			return err
   101  		}
   102  	}
   103  	auth := string(passwords.Bytes())
   104  
   105  	secretData := make(map[string][]byte)
   106  	secretData["auth"] = []byte(auth)
   107  	secret, err := controller.secretLister.Secrets(vmo.Namespace).Get(vmo.Spec.SecretName)
   108  	//When secret exists, k8s api returns a nil err obj.
   109  	if err == nil {
   110  		isEqual := reflect.DeepEqual(secretData, secret.Data)
   111  		if !isEqual {
   112  			secret.Data = secretData
   113  			_, err = controller.kubeclientset.CoreV1().Secrets(vmo.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
   114  			if err != nil {
   115  				return controller.log.ErrorfNewErr("Failed to update a basic auth secret %s:%s: %v", vmo.Namespace, vmo.Spec.SecretName, err)
   116  			}
   117  		}
   118  		return nil
   119  	}
   120  	// set a name for our VMO secret
   121  	// create the secret based on the Username/Password passed in the spec
   122  	secret, err = secrets.New(vmo, vmo.Spec.SecretName, []byte(auth))
   123  	if err != nil {
   124  		return controller.log.ErrorfNewErr("Failed creating a password hash, err: %v", err)
   125  	}
   126  	secretOut, err := controller.kubeclientset.CoreV1().Secrets(vmo.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
   127  	if err != nil {
   128  		return controller.log.ErrorfNewErr("Failed creating secret %s/%s: %v", vmo.Namespace, vmo.Spec.SecretName, err)
   129  	}
   130  	controller.log.Debugf("Created secret: %s", secretOut.Name)
   131  
   132  	// Delete Auth secrets if it is not supposed to exists
   133  
   134  	secretsNames := []string{secret.Name, vmo.Name + "-tls"}
   135  	selector := labels.SelectorFromSet(map[string]string{constants.VMOLabel: vmo.Name})
   136  	secretList, err := controller.secretLister.Secrets(vmo.Namespace).List(selector)
   137  	if err != nil {
   138  		return controller.log.ErrorfNewErr("Failed listing secrets in namespace %s: %v", vmo.Namespace, err)
   139  	}
   140  	for _, existedSecret := range secretList {
   141  		if !contains(secretsNames, existedSecret.Name) {
   142  			controller.log.Debugf("Deleting secret %s", existedSecret.Name)
   143  			err := controller.kubeclientset.CoreV1().Secrets(vmo.Namespace).Delete(context.TODO(), existedSecret.Name, metav1.DeleteOptions{})
   144  			if err != nil {
   145  				return controller.log.ErrorfNewErr("Failed to delete secret %s/%s: %v", vmo.Namespace, existedSecret.Name, err)
   146  			}
   147  		}
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  // CreateOrUpdateTLSSecrets create/updates TLS secrets
   154  func CreateOrUpdateTLSSecrets(controller *Controller, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error {
   155  
   156  	if vmo.Spec.AutoSecret {
   157  		controller.log.Debug("Not explicitly creating TLS secret, we expect it to be auto-generated")
   158  		// by setting the AutoSecret to true we ask that a certificate be made for us
   159  		// currently the mechanism relies on ingressShim part of cert-manager to notice the
   160  		// annotation we set on the ingress rule which is set off by AutoSecret being true
   161  		return nil
   162  	}
   163  	//get tlsCrt from vmoSecrets
   164  	tlsCrt, err := controller.loadSecretData(vmo.Namespace,
   165  		vmo.Spec.SecretsName, constants.TLSCRTName)
   166  	if err != nil {
   167  		return controller.log.ErrorfNewErr("Failed getting tls.crt data using name %s, from secret %s/%s: %v",
   168  			constants.TLSCRTName, vmo.Namespace, vmo.Spec.SecretsName, err)
   169  	}
   170  	//get tlsKey from vmoSecrets
   171  	tlsKey, err := controller.loadSecretData(vmo.Namespace,
   172  		vmo.Spec.SecretsName, constants.TLSKeyName)
   173  	if err != nil {
   174  		return controller.log.ErrorfNewErr("Failed getting tls.key data using name %s, from secret %s/%s: %v",
   175  			constants.TLSKeyName, vmo.Namespace, vmo.Spec.SecretsName, err)
   176  	}
   177  
   178  	if len(tlsCrt) != 0 && len(tlsKey) != 0 {
   179  		secretData := make(map[string][]byte)
   180  		secret, err := controller.secretLister.Secrets(vmo.Namespace).Get(vmo.Name + "-tls")
   181  		//When secret exists, k8s api returns a nil err obj.
   182  		if err == nil {
   183  			secretData["tls.crt"] = tlsCrt
   184  			secretData["tls.key"] = tlsKey
   185  			isSecretDataEqual := reflect.DeepEqual(secretData, secret.Data)
   186  			if !isSecretDataEqual {
   187  				secret.Data = secretData
   188  				_, err = controller.kubeclientset.CoreV1().Secrets(vmo.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
   189  				if err != nil {
   190  					return controller.log.ErrorfNewErr("Failed to updated basic auth secret %s/%s: err: %v", vmo.Namespace, vmo.Name+"-tls", err)
   191  				}
   192  			}
   193  			return nil
   194  		}
   195  		secret, err = secrets.NewTLS(vmo, vmo.Name+"-tls", secretData)
   196  		if err != nil {
   197  			return controller.log.ErrorfNewErr("Failed trying to create a password hash: %v", err)
   198  		}
   199  		secretOut, err := controller.kubeclientset.CoreV1().Secrets(vmo.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
   200  		if err != nil {
   201  			return controller.log.ErrorfNewErr("Failed to create secret %s/%s: %v", vmo.Namespace, vmo.Name+"-tls", err)
   202  		}
   203  		controller.log.Debugf("Create TLS secret: %s", secretOut.Name)
   204  	}
   205  	return nil
   206  }
   207  
   208  func (c *Controller) loadSecretData(ns, secretName, secretField string) ([]byte, error) {
   209  	secret, err := c.secretLister.Secrets(ns).Get(secretName)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	if data, ok := secret.Data[secretField]; ok {
   215  		return data, nil
   216  	}
   217  	return nil, nil
   218  }
   219  
   220  func (c *Controller) loadAllAuthSecretData(ns, secretName string) (map[string]string, error) {
   221  	secret, err := c.secretLister.Secrets(ns).Get(secretName)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	dataMap := secret.Data
   227  
   228  	_, ok := dataMap["username"]
   229  	if !ok {
   230  		return nil, errors.New("Failed: The default username is not defined in VMO secrets")
   231  	}
   232  
   233  	m := make(map[string]string)
   234  	re := regexp.MustCompile("[0-9]+")
   235  	var pwd []byte
   236  	for key, value := range dataMap {
   237  		if !strings.Contains(strings.ToUpper(key), strings.ToUpper("username")) {
   238  			continue
   239  		}
   240  		//Below Regular Expression returns any number present in the string after username
   241  		userIndex := re.FindAllString(key, -1)
   242  		if len(userIndex) == 0 {
   243  			//Default User does not have any number appended
   244  			pwd, ok = dataMap["password"]
   245  			if !ok {
   246  				return nil, errors.New("Failed: The default password is not defined in VMO secrets")
   247  			}
   248  			m[string(value)] = string(pwd)
   249  		} else if len(userIndex) == 1 {
   250  			//Other users in the format username1,username2 etc, Have a integer appended at the end
   251  			pwd, ok = dataMap["password"+userIndex[0]]
   252  			if !ok {
   253  				return nil, errors.New("error: The password is in the wrong format in VMO secrets, should be i.e. password:p1, password2:u2, password3:u3 etc")
   254  			}
   255  			m[string(value)] = string(pwd)
   256  		} else {
   257  			// We should never reach here if the usernames are defined correctly in the secret file
   258  			return nil, errors.New("Failed: The username is in the wrong format in VMO secrets, More than 1 number in map key")
   259  		}
   260  	}
   261  
   262  	return m, nil
   263  }