github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/controller/pxc/secrets.go (about)

     1  package pxc
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"math/big"
     8  	mrand "math/rand"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  	corev1 "k8s.io/api/core/v1"
    14  	k8serror "k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/types"
    17  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    18  
    19  	api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
    20  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/users"
    21  )
    22  
    23  const internalSecretsPrefix = "internal-"
    24  
    25  func (r *ReconcilePerconaXtraDBCluster) reconcileUsersSecret(ctx context.Context, cr *api.PerconaXtraDBCluster) error {
    26  	log := logf.FromContext(ctx)
    27  
    28  	secretObj := new(corev1.Secret)
    29  	err := r.client.Get(context.TODO(),
    30  		types.NamespacedName{
    31  			Namespace: cr.Namespace,
    32  			Name:      cr.Spec.SecretsName,
    33  		},
    34  		secretObj,
    35  	)
    36  	if err == nil {
    37  		if err := validatePasswords(secretObj); err != nil {
    38  			return errors.Wrap(err, "validate passwords")
    39  		}
    40  		isChanged, err := setUserSecretDefaults(secretObj)
    41  		if err != nil {
    42  			return errors.Wrap(err, "set user secret defaults")
    43  		}
    44  		if isChanged {
    45  			err := r.client.Update(context.TODO(), secretObj)
    46  			if err == nil {
    47  				log.Info("User secrets updated", "secrets", cr.Spec.SecretsName)
    48  			}
    49  			return err
    50  		}
    51  		return nil
    52  	} else if !k8serror.IsNotFound(err) {
    53  		return errors.Wrap(err, "get secret")
    54  	}
    55  
    56  	secretObj = &corev1.Secret{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Name:      cr.Spec.SecretsName,
    59  			Namespace: cr.Namespace,
    60  		},
    61  		Type: corev1.SecretTypeOpaque,
    62  	}
    63  
    64  	if _, err = setUserSecretDefaults(secretObj); err != nil {
    65  		return errors.Wrap(err, "set user secret defaults")
    66  	}
    67  
    68  	err = r.client.Create(context.TODO(), secretObj)
    69  	if err != nil {
    70  		return fmt.Errorf("create Users secret: %v", err)
    71  	}
    72  
    73  	log.Info("Created user secrets", "secrets", cr.Spec.SecretsName)
    74  	return nil
    75  }
    76  
    77  func setUserSecretDefaults(secret *corev1.Secret) (isChanged bool, err error) {
    78  	if secret.Data == nil {
    79  		secret.Data = make(map[string][]byte)
    80  	}
    81  	users := []string{users.Root, users.Xtrabackup, users.Monitor, users.ProxyAdmin, users.Operator, users.Replication}
    82  	for _, user := range users {
    83  		if pass, ok := secret.Data[user]; !ok || len(pass) == 0 {
    84  			secret.Data[user], err = generatePass()
    85  			if err != nil {
    86  				return false, errors.Wrapf(err, "create %s users password", user)
    87  			}
    88  
    89  			isChanged = true
    90  		}
    91  	}
    92  	return
    93  }
    94  
    95  const (
    96  	passwordMaxLen = 20
    97  	passwordMinLen = 16
    98  	passSymbols    = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
    99  		"abcdefghijklmnopqrstuvwxyz" +
   100  		"0123456789" +
   101  		"!#$%&()*+,-.<=>?@[]^_{}~"
   102  )
   103  
   104  // generatePass generates a random password
   105  func generatePass() ([]byte, error) {
   106  	mrand.Seed(time.Now().UnixNano())
   107  	ln := mrand.Intn(passwordMaxLen-passwordMinLen) + passwordMinLen
   108  	b := make([]byte, ln)
   109  	for i := 0; i < ln; i++ {
   110  		randInt, err := rand.Int(rand.Reader, big.NewInt(int64(len(passSymbols))))
   111  		if err != nil {
   112  			return nil, errors.Wrap(err, "get rand int")
   113  		}
   114  		b[i] = passSymbols[randInt.Int64()]
   115  	}
   116  
   117  	return b, nil
   118  }
   119  
   120  func validatePasswords(secret *corev1.Secret) error {
   121  	for user, pass := range secret.Data {
   122  		switch user {
   123  		case users.ProxyAdmin:
   124  			if strings.ContainsAny(string(pass), ";:") {
   125  				return errors.New("invalid proxyadmin password, don't use ';' or ':'")
   126  			}
   127  		default:
   128  			continue
   129  		}
   130  	}
   131  
   132  	return nil
   133  }