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

     1  package pxc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha256"
     7  	"encoding/json"
     8  	"fmt"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/go-version"
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/sync/errgroup"
    15  	corev1 "k8s.io/api/core/v1"
    16  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"sigs.k8s.io/controller-runtime/pkg/client"
    20  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    21  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    22  
    23  	api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
    24  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/statefulset"
    25  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/users"
    26  )
    27  
    28  var mysql80 = version.Must(version.NewVersion("8.0.0"))
    29  
    30  // https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html#priv_system-user
    31  var privSystemUserAddedIn = version.Must(version.NewVersion("8.0.16"))
    32  
    33  var PassNotPropagatedError = errors.New("password not yet propagated")
    34  
    35  type userUpdateActions struct {
    36  	restartPXC            bool
    37  	restartProxy          bool
    38  	updateReplicationPass bool
    39  }
    40  
    41  type ReconcileUsersResult struct {
    42  	pxcAnnotations            map[string]string
    43  	proxyAnnotations          map[string]string
    44  	updateReplicationPassword bool
    45  }
    46  
    47  func (r *ReconcilePerconaXtraDBCluster) reconcileUsers(ctx context.Context, cr *api.PerconaXtraDBCluster) (*ReconcileUsersResult, error) {
    48  	log := logf.FromContext(ctx)
    49  
    50  	secrets := corev1.Secret{}
    51  	err := r.client.Get(context.TODO(),
    52  		types.NamespacedName{
    53  			Namespace: cr.Namespace,
    54  			Name:      cr.Spec.SecretsName,
    55  		},
    56  		&secrets,
    57  	)
    58  	if err != nil && k8serrors.IsNotFound(err) {
    59  		return nil, nil
    60  	} else if err != nil {
    61  		return nil, errors.Wrapf(err, "get sys users secret '%s'", cr.Spec.SecretsName)
    62  	}
    63  
    64  	internalSecretName := internalSecretsPrefix + cr.Name
    65  
    66  	internalSecrets := corev1.Secret{}
    67  	err = r.client.Get(context.TODO(),
    68  		types.NamespacedName{
    69  			Namespace: cr.Namespace,
    70  			Name:      internalSecretName,
    71  		},
    72  		&internalSecrets,
    73  	)
    74  	if err != nil && !k8serrors.IsNotFound(err) {
    75  		return nil, errors.Wrap(err, "get internal sys users secret")
    76  	}
    77  
    78  	if k8serrors.IsNotFound(err) {
    79  		is := secrets.DeepCopy()
    80  		is.ObjectMeta = metav1.ObjectMeta{
    81  			Name:      internalSecretName,
    82  			Namespace: cr.Namespace,
    83  		}
    84  		err = r.client.Create(context.TODO(), is)
    85  		if err != nil {
    86  			return nil, errors.Wrap(err, "create internal sys users secret")
    87  		}
    88  		return nil, nil
    89  	}
    90  
    91  	mysqlVersion := cr.Status.PXC.Version
    92  	if mysqlVersion == "" {
    93  		var err error
    94  		mysqlVersion, err = r.mysqlVersion(ctx, cr, statefulset.NewNode(cr))
    95  		if err != nil {
    96  			if errors.Is(err, versionNotReadyErr) {
    97  				return nil, nil
    98  			}
    99  			return nil, errors.Wrap(err, "retrieving pxc version")
   100  		}
   101  	}
   102  
   103  	ver, err := version.NewVersion(mysqlVersion)
   104  	if err != nil {
   105  		return nil, errors.Wrap(err, "invalid pxc version")
   106  	}
   107  
   108  	var actions *userUpdateActions
   109  	if ver.GreaterThanOrEqual(mysql80) {
   110  		actions, err = r.updateUsers(ctx, cr, &secrets, &internalSecrets)
   111  		if err != nil {
   112  			return nil, errors.Wrap(err, "manage sys users")
   113  		}
   114  	} else {
   115  		actions, err = r.updateUsersWithoutDP(ctx, cr, &secrets, &internalSecrets)
   116  		if err != nil {
   117  			return nil, errors.Wrap(err, "manage sys users")
   118  		}
   119  	}
   120  
   121  	newSysData, err := json.Marshal(secrets.Data)
   122  	if err != nil {
   123  		return nil, errors.Wrap(err, "marshal sys secret data")
   124  	}
   125  	newSecretDataHash := sha256Hash(newSysData)
   126  
   127  	result := &ReconcileUsersResult{
   128  		updateReplicationPassword: actions.updateReplicationPass,
   129  	}
   130  
   131  	if actions.restartProxy {
   132  		log.Info("Proxy pods will be restarted", "last-applied-secret", newSecretDataHash)
   133  		result.proxyAnnotations = map[string]string{"last-applied-secret": newSecretDataHash}
   134  	}
   135  	if actions.restartPXC {
   136  		log.Info("PXC pods will be restarted", "last-applied-secret", newSecretDataHash)
   137  		result.pxcAnnotations = map[string]string{"last-applied-secret": newSecretDataHash}
   138  	}
   139  
   140  	return result, nil
   141  }
   142  
   143  func sha256Hash(data []byte) string {
   144  	return fmt.Sprintf("%x", sha256.Sum256(data))
   145  }
   146  
   147  func (r *ReconcilePerconaXtraDBCluster) updateUsers(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret) (*userUpdateActions, error) {
   148  	res := &userUpdateActions{}
   149  
   150  	for _, u := range users.UserNames {
   151  		if _, ok := secrets.Data[u]; !ok {
   152  			continue
   153  		}
   154  
   155  		switch u {
   156  		case users.Root:
   157  			if err := r.handleRootUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   158  				return res, err
   159  			}
   160  		case users.Operator:
   161  			if err := r.handleOperatorUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   162  				return res, err
   163  			}
   164  		case users.Monitor:
   165  			if err := r.handleMonitorUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   166  				if errors.Is(err, PassNotPropagatedError) {
   167  					continue
   168  				}
   169  				return res, err
   170  			}
   171  		case users.Xtrabackup:
   172  			if err := r.handleXtrabackupUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   173  				return res, err
   174  			}
   175  		case users.Replication:
   176  			if err := r.handleReplicationUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   177  				return res, err
   178  			}
   179  		case users.ProxyAdmin:
   180  			if err := r.handleProxyadminUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   181  				return res, err
   182  			}
   183  		case users.PMMServer, users.PMMServerKey:
   184  			if err := r.handlePMMUser(ctx, cr, secrets, internalSecrets, res); err != nil {
   185  				return res, err
   186  			}
   187  		}
   188  	}
   189  
   190  	return res, nil
   191  }
   192  
   193  func (r *ReconcilePerconaXtraDBCluster) handleRootUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   194  	log := logf.FromContext(ctx)
   195  
   196  	user := &users.SysUser{
   197  		Name:  users.Root,
   198  		Pass:  string(secrets.Data[users.Root]),
   199  		Hosts: []string{"localhost", "%"},
   200  	}
   201  
   202  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   203  		return nil
   204  	}
   205  
   206  	if err := r.updateUserPassExpirationPolicy(ctx, cr, internalSecrets, user); err != nil {
   207  		return err
   208  	}
   209  
   210  	passDiscarded, err := r.isOldPasswordDiscarded(cr, internalSecrets, user)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && passDiscarded {
   216  		return nil
   217  	}
   218  
   219  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && !passDiscarded {
   220  		err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   221  		if err != nil {
   222  			return errors.Wrap(err, "discard old pass")
   223  		}
   224  		log.Info("Old password discarded", "user", user.Name)
   225  
   226  		return nil
   227  	}
   228  
   229  	log.Info("Password changed, updating user", "user", user.Name)
   230  
   231  	err = r.updateUserPassWithRetention(cr, secrets, internalSecrets, user)
   232  	if err != nil {
   233  		return errors.Wrap(err, "update root users pass")
   234  	}
   235  	log.Info("Password updated", "user", user.Name)
   236  
   237  	if err := r.updateMySQLInitFile(ctx, cr, internalSecrets, user); err != nil {
   238  		return errors.Wrap(err, "update mysql init file")
   239  	}
   240  
   241  	err = r.syncPXCUsersWithProxySQL(ctx, cr)
   242  	if err != nil {
   243  		return errors.Wrap(err, "sync users")
   244  	}
   245  
   246  	orig := internalSecrets.DeepCopy()
   247  	internalSecrets.Data[user.Name] = secrets.Data[user.Name]
   248  	err = r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   249  	if err != nil {
   250  		return errors.Wrap(err, "update internal secrets root user password")
   251  	}
   252  	log.Info("Internal secrets updated", "user", user.Name)
   253  
   254  	err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   255  	if err != nil {
   256  		return errors.Wrap(err, "discard old password")
   257  	}
   258  	log.Info("Old password discarded", "user", user.Name)
   259  
   260  	return nil
   261  }
   262  
   263  func (r *ReconcilePerconaXtraDBCluster) handleOperatorUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   264  	log := logf.FromContext(ctx)
   265  
   266  	user := &users.SysUser{
   267  		Name:  users.Operator,
   268  		Pass:  string(secrets.Data[users.Operator]),
   269  		Hosts: []string{"%"},
   270  	}
   271  
   272  	if cr.Status.PXC.Ready > 0 {
   273  		err := r.manageOperatorAdminUser(ctx, cr, secrets, internalSecrets)
   274  		if err != nil {
   275  			return errors.Wrap(err, "manage operator admin user")
   276  		}
   277  
   278  		if err := r.updateUserPassExpirationPolicy(ctx, cr, internalSecrets, user); err != nil {
   279  			return err
   280  		}
   281  	}
   282  
   283  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   284  		return nil
   285  	}
   286  
   287  	passDiscarded, err := r.isOldPasswordDiscarded(cr, internalSecrets, user)
   288  	if err != nil {
   289  		return err
   290  	}
   291  
   292  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && passDiscarded {
   293  		return nil
   294  	}
   295  
   296  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && !passDiscarded {
   297  		err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   298  		if err != nil {
   299  			return errors.Wrap(err, "discard old pass")
   300  		}
   301  		log.Info("Old password discarded", "user", user.Name)
   302  
   303  		return nil
   304  	}
   305  
   306  	log.Info("Password changed, updating user", "user", user.Name)
   307  
   308  	err = r.updateUserPassWithRetention(cr, secrets, internalSecrets, user)
   309  	if err != nil {
   310  		return errors.Wrap(err, "update operator users pass")
   311  	}
   312  	log.Info("Password updated", "user", user.Name)
   313  
   314  	if err := r.updateMySQLInitFile(ctx, cr, internalSecrets, user); err != nil {
   315  		return errors.Wrap(err, "update mysql init file")
   316  	}
   317  
   318  	orig := internalSecrets.DeepCopy()
   319  	internalSecrets.Data[user.Name] = secrets.Data[user.Name]
   320  	err = r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   321  	if err != nil {
   322  		return errors.Wrap(err, "update internal users secrets operator user password")
   323  	}
   324  	log.Info("Internal secrets updated", "user", user.Name)
   325  
   326  	actions.restartProxy = true
   327  
   328  	err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   329  	if err != nil {
   330  		return errors.Wrap(err, "discard operator old password")
   331  	}
   332  	log.Info("Old password discarded", "user", user.Name)
   333  
   334  	return nil
   335  }
   336  
   337  // manageOperatorAdminUser ensures that operator user is always present and with the right privileges
   338  func (r *ReconcilePerconaXtraDBCluster) manageOperatorAdminUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret) error {
   339  	log := logf.FromContext(ctx)
   340  
   341  	pass, existInSys := secrets.Data[users.Operator]
   342  	_, existInInternal := internalSecrets.Data[users.Operator]
   343  	if existInSys && !existInInternal {
   344  		if internalSecrets.Data == nil {
   345  			internalSecrets.Data = make(map[string][]byte)
   346  		}
   347  		internalSecrets.Data[users.Operator] = pass
   348  		return nil
   349  	}
   350  	if existInSys {
   351  		return nil
   352  	}
   353  
   354  	pass, err := generatePass()
   355  	if err != nil {
   356  		return errors.Wrap(err, "generate password")
   357  	}
   358  	addr := cr.Name + "-pxc." + cr.Namespace
   359  	if cr.CompareVersionWith("1.6.0") >= 0 {
   360  		addr = cr.Name + "-pxc-unready." + cr.Namespace + ":33062"
   361  	}
   362  	um, err := users.NewManager(addr, users.Root, string(secrets.Data[users.Root]), cr.Spec.PXC.ReadinessProbes.TimeoutSeconds)
   363  	if err != nil {
   364  		return errors.Wrap(err, "new users manager")
   365  	}
   366  	defer um.Close()
   367  
   368  	err = um.CreateOperatorUser(string(pass))
   369  	if err != nil {
   370  		return errors.Wrap(err, "create operator user")
   371  	}
   372  
   373  	secrets.Data[users.Operator] = pass
   374  	internalSecrets.Data[users.Operator] = pass
   375  
   376  	err = r.client.Update(context.TODO(), secrets)
   377  	if err != nil {
   378  		return errors.Wrap(err, "update sys users secret")
   379  	}
   380  	err = r.client.Update(context.TODO(), internalSecrets)
   381  	if err != nil {
   382  		return errors.Wrap(err, "update internal users secret")
   383  	}
   384  
   385  	log.Info("User created and privileges granted", "user", users.Operator)
   386  	return nil
   387  }
   388  
   389  func (r *ReconcilePerconaXtraDBCluster) handleMonitorUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   390  	log := logf.FromContext(ctx)
   391  
   392  	user := &users.SysUser{
   393  		Name:  users.Monitor,
   394  		Pass:  string(secrets.Data[users.Monitor]),
   395  		Hosts: []string{"%"},
   396  	}
   397  
   398  	if cr.Status.PXC.Ready > 0 {
   399  		if err := r.updateUserPassExpirationPolicy(ctx, cr, internalSecrets, user); err != nil {
   400  			return err
   401  		}
   402  
   403  		um, err := getUserManager(cr, internalSecrets)
   404  		if err != nil {
   405  			return err
   406  		}
   407  		defer um.Close()
   408  
   409  		if cr.CompareVersionWith("1.6.0") >= 0 {
   410  			err := r.updateMonitorUserGrant(ctx, cr, internalSecrets, um)
   411  			if err != nil {
   412  				return errors.Wrap(err, "update monitor user grant")
   413  			}
   414  		}
   415  
   416  		if cr.CompareVersionWith("1.10.0") >= 0 {
   417  			mysqlVersion := cr.Status.PXC.Version
   418  			if mysqlVersion == "" {
   419  				var err error
   420  				mysqlVersion, err = r.mysqlVersion(ctx, cr, statefulset.NewNode(cr))
   421  				if err != nil {
   422  					if errors.Is(err, versionNotReadyErr) {
   423  						return nil
   424  					}
   425  					return errors.Wrap(err, "retrieving pxc version")
   426  				}
   427  			}
   428  
   429  			if mysqlVersion != "" {
   430  				ver, err := version.NewVersion(mysqlVersion)
   431  				if err != nil {
   432  					return errors.Wrap(err, "invalid pxc version")
   433  				}
   434  
   435  				if !ver.LessThan(privSystemUserAddedIn) {
   436  					if err := r.grantMonitorUserPrivilege(ctx, cr, internalSecrets, um); err != nil {
   437  						return errors.Wrap(err, "monitor user grant system privilege")
   438  					}
   439  				}
   440  			}
   441  		}
   442  	}
   443  
   444  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   445  		return nil
   446  	}
   447  
   448  	passDiscarded, err := r.isOldPasswordDiscarded(cr, internalSecrets, user)
   449  	if err != nil {
   450  		return err
   451  	}
   452  
   453  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && passDiscarded {
   454  		return nil
   455  	}
   456  
   457  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && !passDiscarded {
   458  		log.Info("Password updated but old one not discarded", "user", user.Name)
   459  
   460  		passPropagated, err := r.isPassPropagated(cr, user)
   461  		if err != nil {
   462  			return errors.Wrap(err, "is password propagated")
   463  		}
   464  		if !passPropagated {
   465  			return PassNotPropagatedError
   466  		}
   467  
   468  		actions.restartProxy = true
   469  		if cr.Spec.PMM != nil && cr.Spec.PMM.IsEnabled(internalSecrets) {
   470  			actions.restartPXC = true
   471  		}
   472  
   473  		err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   474  		if err != nil {
   475  			return errors.Wrap(err, "discard old pass")
   476  		}
   477  		log.Info("Old password discarded", "user", user.Name)
   478  
   479  		return nil
   480  	}
   481  
   482  	log.Info("Password changed, updating user", "user", user.Name)
   483  
   484  	err = r.updateUserPassWithRetention(cr, secrets, internalSecrets, user)
   485  	if err != nil {
   486  		return errors.Wrap(err, "update monitor users pass")
   487  	}
   488  	log.Info("Password updated", "user", user.Name)
   489  
   490  	if err := r.updateMySQLInitFile(ctx, cr, internalSecrets, user); err != nil {
   491  		return errors.Wrap(err, "update mysql init file")
   492  	}
   493  
   494  	if cr.Spec.ProxySQLEnabled() {
   495  		err := r.updateProxyUser(cr, internalSecrets, user)
   496  		if err != nil {
   497  			return errors.Wrap(err, "update monitor users pass")
   498  		}
   499  		log.Info("Proxy user updated", "user", user.Name)
   500  	}
   501  
   502  	actions.restartProxy = true
   503  	if cr.Spec.PMM != nil && cr.Spec.PMM.IsEnabled(internalSecrets) {
   504  		actions.restartPXC = true
   505  	}
   506  
   507  	orig := internalSecrets.DeepCopy()
   508  	internalSecrets.Data[user.Name] = secrets.Data[user.Name]
   509  	err = r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   510  	if err != nil {
   511  		return errors.Wrap(err, "update internal users secrets monitor user password")
   512  	}
   513  	log.Info("Internal secrets updated", "user", user.Name)
   514  
   515  	passPropagated, err := r.isPassPropagated(cr, user)
   516  	if err != nil {
   517  		return errors.Wrap(err, "is password propagated")
   518  	}
   519  	if !passPropagated {
   520  		return PassNotPropagatedError
   521  	}
   522  
   523  	err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   524  	if err != nil {
   525  		return errors.Wrap(err, "discard monitor old password")
   526  	}
   527  	log.Info("Old password discarded", "user", user.Name)
   528  
   529  	return nil
   530  }
   531  
   532  func (r *ReconcilePerconaXtraDBCluster) updateMonitorUserGrant(ctx context.Context, cr *api.PerconaXtraDBCluster, internalSysSecretObj *corev1.Secret, um *users.Manager) error {
   533  	log := logf.FromContext(ctx)
   534  
   535  	annotationName := "grant-for-1.6.0-monitor-user"
   536  	if internalSysSecretObj.Annotations[annotationName] == "done" {
   537  		return nil
   538  	}
   539  
   540  	err := um.Update160MonitorUserGrant(string(internalSysSecretObj.Data[users.Monitor]))
   541  	if err != nil {
   542  		return errors.Wrap(err, "update monitor grant")
   543  	}
   544  
   545  	if internalSysSecretObj.Annotations == nil {
   546  		internalSysSecretObj.Annotations = make(map[string]string)
   547  	}
   548  
   549  	internalSysSecretObj.Annotations[annotationName] = "done"
   550  	err = r.client.Update(context.TODO(), internalSysSecretObj)
   551  	if err != nil {
   552  		return errors.Wrap(err, "update internal sys users secret annotation")
   553  	}
   554  
   555  	log.Info("User monitor: granted privileges")
   556  	return nil
   557  }
   558  
   559  func (r *ReconcilePerconaXtraDBCluster) handleXtrabackupUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   560  	log := logf.FromContext(ctx)
   561  
   562  	user := &users.SysUser{
   563  		Name:  users.Xtrabackup,
   564  		Pass:  string(secrets.Data[users.Xtrabackup]),
   565  		Hosts: []string{"localhost"},
   566  	}
   567  
   568  	if cr.CompareVersionWith("1.7.0") >= 0 {
   569  		user.Hosts = []string{"%"}
   570  	}
   571  
   572  	if cr.Status.PXC.Ready > 0 {
   573  		if err := r.updateUserPassExpirationPolicy(ctx, cr, internalSecrets, user); err != nil {
   574  			return err
   575  		}
   576  
   577  		if cr.CompareVersionWith("1.7.0") >= 0 {
   578  			// xtrabackup user need more grants for work in version more then 1.6.0
   579  			err := r.updateXtrabackupUserGrant(ctx, cr, internalSecrets)
   580  			if err != nil {
   581  				return errors.Wrap(err, "update xtrabackup user grant")
   582  			}
   583  		}
   584  	}
   585  
   586  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   587  		return nil
   588  	}
   589  
   590  	passDiscarded, err := r.isOldPasswordDiscarded(cr, internalSecrets, user)
   591  	if err != nil {
   592  		return err
   593  	}
   594  
   595  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && passDiscarded {
   596  		return nil
   597  	}
   598  
   599  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && !passDiscarded {
   600  		err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   601  		if err != nil {
   602  			return errors.Wrap(err, "discard old pass")
   603  		}
   604  		log.Info("Old password discarded", "user", user.Name)
   605  
   606  		return nil
   607  	}
   608  
   609  	log.Info("Password changed, updating user", "user", user.Name)
   610  
   611  	err = r.updateUserPassWithRetention(cr, secrets, internalSecrets, user)
   612  	if err != nil {
   613  		return errors.Wrap(err, "update xtrabackup users pass")
   614  	}
   615  	log.Info("Password updated", "user", user.Name)
   616  
   617  	if err := r.updateMySQLInitFile(ctx, cr, internalSecrets, user); err != nil {
   618  		return errors.Wrap(err, "update mysql init file")
   619  	}
   620  
   621  	orig := internalSecrets.DeepCopy()
   622  	internalSecrets.Data[user.Name] = secrets.Data[user.Name]
   623  	err = r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   624  	if err != nil {
   625  		return errors.Wrap(err, "update internal users secrets xtrabackup user password")
   626  	}
   627  	log.Info("Internal secrets updated", "user", user.Name)
   628  
   629  	err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   630  	if err != nil {
   631  		return errors.Wrap(err, "discard xtrabackup old pass")
   632  	}
   633  	log.Info("Old password discarded", "user", user.Name)
   634  
   635  	actions.restartPXC = true
   636  	return nil
   637  }
   638  
   639  func (r *ReconcilePerconaXtraDBCluster) updateXtrabackupUserGrant(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets *corev1.Secret) error {
   640  	log := logf.FromContext(ctx)
   641  
   642  	annotationName := "grant-for-1.7.0-xtrabackup-user"
   643  	if secrets.Annotations[annotationName] == "done" {
   644  		return nil
   645  	}
   646  
   647  	um, err := getUserManager(cr, secrets)
   648  	if err != nil {
   649  		return err
   650  	}
   651  	defer um.Close()
   652  
   653  	err = um.Update170XtrabackupUser(string(secrets.Data[users.Xtrabackup]))
   654  	if err != nil {
   655  		return errors.Wrap(err, "update xtrabackup grant")
   656  	}
   657  
   658  	if secrets.Annotations == nil {
   659  		secrets.Annotations = make(map[string]string)
   660  	}
   661  
   662  	secrets.Annotations[annotationName] = "done"
   663  	err = r.client.Update(context.TODO(), secrets)
   664  	if err != nil {
   665  		return errors.Wrap(err, "update internal sys users secret annotation")
   666  	}
   667  
   668  	log.Info("User xtrabackup: granted privileges")
   669  	return nil
   670  }
   671  
   672  func (r *ReconcilePerconaXtraDBCluster) handleReplicationUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   673  	log := logf.FromContext(ctx)
   674  
   675  	if cr.CompareVersionWith("1.9.0") < 0 {
   676  		return nil
   677  	}
   678  
   679  	user := &users.SysUser{
   680  		Name:  users.Replication,
   681  		Pass:  string(secrets.Data[users.Replication]),
   682  		Hosts: []string{"%"},
   683  	}
   684  
   685  	if cr.Status.PXC.Ready > 0 {
   686  		err := r.manageReplicationUser(ctx, cr, secrets, internalSecrets)
   687  		if err != nil {
   688  			return errors.Wrap(err, "manage replication user")
   689  		}
   690  
   691  		if err := r.updateUserPassExpirationPolicy(ctx, cr, internalSecrets, user); err != nil {
   692  			return err
   693  		}
   694  	}
   695  
   696  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   697  		return nil
   698  	}
   699  
   700  	passDiscarded, err := r.isOldPasswordDiscarded(cr, internalSecrets, user)
   701  	if err != nil {
   702  		return err
   703  	}
   704  
   705  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && passDiscarded {
   706  		return nil
   707  	}
   708  
   709  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) && !passDiscarded {
   710  		err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   711  		if err != nil {
   712  			return errors.Wrap(err, "discard old pass")
   713  		}
   714  		log.Info("Old password discarded", "user", user.Name)
   715  
   716  		return nil
   717  	}
   718  
   719  	log.Info("Password changed, updating user", "user", user.Name)
   720  
   721  	err = r.updateUserPassWithRetention(cr, secrets, internalSecrets, user)
   722  	if err != nil {
   723  		return errors.Wrap(err, "update replication users pass")
   724  	}
   725  	log.Info("Password updated", "user", user.Name)
   726  
   727  	if err := r.updateMySQLInitFile(ctx, cr, internalSecrets, user); err != nil {
   728  		return errors.Wrap(err, "update mysql init file")
   729  	}
   730  
   731  	orig := internalSecrets.DeepCopy()
   732  	internalSecrets.Data[user.Name] = secrets.Data[user.Name]
   733  	err = r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   734  	if err != nil {
   735  		return errors.Wrap(err, "update internal users secrets replication user password")
   736  	}
   737  	log.Info("Internal secrets updated", "user", user.Name)
   738  
   739  	err = r.discardOldPassword(cr, secrets, internalSecrets, user)
   740  	if err != nil {
   741  		return errors.Wrap(err, "discard replicaiton old pass")
   742  	}
   743  	log.Info("Old password discarded", "user", user.Name)
   744  
   745  	actions.updateReplicationPass = true
   746  	return nil
   747  }
   748  
   749  // manageReplicationUser ensures that replication user is always present and with the right privileges
   750  func (r *ReconcilePerconaXtraDBCluster) manageReplicationUser(ctx context.Context, cr *api.PerconaXtraDBCluster, sysUsersSecretObj, secrets *corev1.Secret) error {
   751  	log := logf.FromContext(ctx)
   752  
   753  	pass, existInSys := sysUsersSecretObj.Data[users.Replication]
   754  	_, existInInternal := secrets.Data[users.Replication]
   755  	if existInSys && !existInInternal {
   756  		if secrets.Data == nil {
   757  			secrets.Data = make(map[string][]byte)
   758  		}
   759  		secrets.Data[users.Replication] = pass
   760  		return nil
   761  	}
   762  	if existInSys {
   763  		return nil
   764  	}
   765  
   766  	um, err := getUserManager(cr, secrets)
   767  	if err != nil {
   768  		return err
   769  	}
   770  	defer um.Close()
   771  
   772  	pass, err = generatePass()
   773  	if err != nil {
   774  		return errors.Wrap(err, "generate password")
   775  	}
   776  
   777  	err = um.CreateReplicationUser(string(pass))
   778  	if err != nil {
   779  		return errors.Wrap(err, "create replication user")
   780  	}
   781  
   782  	sysUsersSecretObj.Data[users.Replication] = pass
   783  	secrets.Data[users.Replication] = pass
   784  
   785  	err = r.client.Update(context.TODO(), sysUsersSecretObj)
   786  	if err != nil {
   787  		return errors.Wrap(err, "update sys users secret")
   788  	}
   789  	err = r.client.Update(context.TODO(), secrets)
   790  	if err != nil {
   791  		return errors.Wrap(err, "update internal users secret")
   792  	}
   793  
   794  	log.Info("User replication: user created and privileges granted")
   795  	return nil
   796  }
   797  
   798  func (r *ReconcilePerconaXtraDBCluster) handleProxyadminUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   799  	log := logf.FromContext(ctx)
   800  
   801  	if !cr.Spec.ProxySQLEnabled() {
   802  		return nil
   803  	}
   804  
   805  	user := &users.SysUser{
   806  		Name: users.ProxyAdmin,
   807  		Pass: string(secrets.Data[users.ProxyAdmin]),
   808  	}
   809  
   810  	if bytes.Equal(secrets.Data[user.Name], internalSecrets.Data[user.Name]) {
   811  		return nil
   812  	}
   813  
   814  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   815  		return nil
   816  	}
   817  
   818  	if err := r.updateUserPassExpirationPolicy(ctx, cr, internalSecrets, user); err != nil {
   819  		return err
   820  	}
   821  
   822  	log.Info("Password changed, updating user", "user", user.Name)
   823  
   824  	err := r.updateProxyUser(cr, internalSecrets, user)
   825  	if err != nil {
   826  		return errors.Wrap(err, "update Proxy users")
   827  	}
   828  	log.Info("Proxy user updated", "user", user.Name)
   829  
   830  	orig := internalSecrets.DeepCopy()
   831  	internalSecrets.Data[user.Name] = secrets.Data[user.Name]
   832  	err = r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   833  	if err != nil {
   834  		return errors.Wrap(err, "update internal users secrets proxyadmin user password")
   835  	}
   836  	log.Info("Internal secrets updated", "user", user.Name)
   837  
   838  	actions.restartProxy = true
   839  
   840  	return nil
   841  }
   842  
   843  func (r *ReconcilePerconaXtraDBCluster) handlePMMUser(ctx context.Context, cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, actions *userUpdateActions) error {
   844  	log := logf.FromContext(ctx)
   845  
   846  	if cr.Spec.PMM == nil || !cr.Spec.PMM.IsEnabled(secrets) {
   847  		return nil
   848  	}
   849  
   850  	if key, ok := secrets.Data[users.PMMServerKey]; ok {
   851  		if _, ok := internalSecrets.Data[users.PMMServerKey]; !ok {
   852  			internalSecrets.Data[users.PMMServerKey] = key
   853  
   854  			err := r.client.Update(context.TODO(), internalSecrets)
   855  			if err != nil {
   856  				return errors.Wrap(err, "update internal users secrets pmm user password")
   857  			}
   858  			log.Info("Internal secrets updated", "user", users.PMMServerKey)
   859  
   860  			return nil
   861  		}
   862  	}
   863  
   864  	name := users.PMMServerKey
   865  	if !cr.Spec.PMM.UseAPI(secrets) {
   866  		name = users.PMMServer
   867  	}
   868  
   869  	if bytes.Equal(secrets.Data[name], internalSecrets.Data[name]) {
   870  		return nil
   871  	}
   872  
   873  	if cr.Status.Status != api.AppStateReady && !r.invalidPasswordApplied(cr.Status) {
   874  		return nil
   875  	}
   876  
   877  	log.Info("Password changed, updating user", "user", name)
   878  
   879  	orig := internalSecrets.DeepCopy()
   880  	internalSecrets.Data[name] = secrets.Data[name]
   881  	err := r.client.Patch(context.TODO(), internalSecrets, client.MergeFrom(orig))
   882  	if err != nil {
   883  		return errors.Wrap(err, "update internal users secrets pmm user password")
   884  	}
   885  	log.Info("Internal secrets updated", "user", name)
   886  
   887  	actions.restartPXC = true
   888  	actions.restartProxy = true
   889  
   890  	return nil
   891  }
   892  
   893  func (r *ReconcilePerconaXtraDBCluster) syncPXCUsersWithProxySQL(ctx context.Context, cr *api.PerconaXtraDBCluster) error {
   894  	log := logf.FromContext(ctx)
   895  
   896  	if !cr.Spec.ProxySQLEnabled() || cr.Status.PXC.Ready < 1 {
   897  		return nil
   898  	}
   899  	if cr.Status.Status != api.AppStateReady || cr.Status.ProxySQL.Status != api.AppStateReady {
   900  		return nil
   901  	}
   902  
   903  	for i := 0; i < int(cr.Spec.ProxySQL.Size); i++ {
   904  		pod := corev1.Pod{}
   905  		err := r.client.Get(context.TODO(),
   906  			types.NamespacedName{
   907  				Namespace: cr.Namespace,
   908  				Name:      cr.Name + "-proxysql-" + strconv.Itoa(i),
   909  			},
   910  			&pod,
   911  		)
   912  		if err != nil && k8serrors.IsNotFound(err) {
   913  			return err
   914  		} else if err != nil {
   915  			return errors.Wrap(err, "get proxysql pod")
   916  		}
   917  		var errb, outb bytes.Buffer
   918  		err = r.clientcmd.Exec(&pod, "proxysql", []string{"proxysql-admin", "--syncusers", "--add-query-rule"}, nil, &outb, &errb, false)
   919  		if err != nil {
   920  			return errors.Errorf("exec syncusers: %v / %s / %s", err, outb.String(), errb.String())
   921  		}
   922  		if len(errb.Bytes()) > 0 {
   923  			return errors.New("syncusers: " + errb.String())
   924  		}
   925  	}
   926  
   927  	log.V(1).Info("PXC users synced with ProxySQL")
   928  	return nil
   929  }
   930  
   931  func (r *ReconcilePerconaXtraDBCluster) updateUserPassWithRetention(cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, user *users.SysUser) error {
   932  	um, err := getUserManager(cr, internalSecrets)
   933  	if err != nil {
   934  		return err
   935  	}
   936  	defer um.Close()
   937  
   938  	err = um.UpdateUserPass(user)
   939  	if err != nil {
   940  		return errors.Wrap(err, "update user pass")
   941  	}
   942  
   943  	return nil
   944  }
   945  
   946  func (r *ReconcilePerconaXtraDBCluster) discardOldPassword(cr *api.PerconaXtraDBCluster, secrets, internalSecrets *corev1.Secret, user *users.SysUser) error {
   947  	um, err := getUserManager(cr, internalSecrets)
   948  	if err != nil {
   949  		return err
   950  	}
   951  	defer um.Close()
   952  
   953  	err = um.DiscardOldPassword(user)
   954  	if err != nil {
   955  		return errors.Wrap(err, fmt.Sprintf("discard old user %s pass", user.Name))
   956  	}
   957  
   958  	return nil
   959  }
   960  
   961  func (r *ReconcilePerconaXtraDBCluster) isOldPasswordDiscarded(cr *api.PerconaXtraDBCluster, secrets *corev1.Secret, user *users.SysUser) (bool, error) {
   962  	um, err := getUserManager(cr, secrets)
   963  	if err != nil {
   964  		return false, err
   965  	}
   966  	defer um.Close()
   967  
   968  	discarded, err := um.IsOldPassDiscarded(user)
   969  	if err != nil {
   970  		return false, errors.Wrap(err, "is old password discarded")
   971  	}
   972  
   973  	return discarded, nil
   974  }
   975  
   976  func (r *ReconcilePerconaXtraDBCluster) isPassPropagated(cr *api.PerconaXtraDBCluster, user *users.SysUser) (bool, error) {
   977  	components := map[string]int32{
   978  		"pxc": cr.Spec.PXC.Size,
   979  	}
   980  
   981  	if cr.HAProxyEnabled() {
   982  		components["haproxy"] = cr.Spec.HAProxy.Size
   983  	}
   984  
   985  	eg := new(errgroup.Group)
   986  
   987  	for component, size := range components {
   988  		comp := component
   989  		compCount := size
   990  		eg.Go(func() error {
   991  			for i := 0; int32(i) < compCount; i++ {
   992  				pod := corev1.Pod{}
   993  				err := r.client.Get(context.TODO(),
   994  					types.NamespacedName{
   995  						Namespace: cr.Namespace,
   996  						Name:      fmt.Sprintf("%s-%s-%d", cr.Name, comp, i),
   997  					},
   998  					&pod,
   999  				)
  1000  				if err != nil && k8serrors.IsNotFound(err) {
  1001  					return err
  1002  				} else if err != nil {
  1003  					return errors.Wrapf(err, "get %s pod", comp)
  1004  				}
  1005  				var errb, outb bytes.Buffer
  1006  				err = r.clientcmd.Exec(&pod, comp, []string{"cat", fmt.Sprintf("/etc/mysql/mysql-users-secret/%s", user.Name)}, nil, &outb, &errb, false)
  1007  				if err != nil {
  1008  					return errors.Errorf("exec cat on %s-%d: %v / %s / %s", comp, i, err, outb.String(), errb.String())
  1009  				}
  1010  				if len(errb.Bytes()) > 0 {
  1011  					return errors.Errorf("cat on %s-%d: %s", comp, i, errb.String())
  1012  				}
  1013  
  1014  				if outb.String() != user.Pass {
  1015  					return PassNotPropagatedError
  1016  				}
  1017  			}
  1018  
  1019  			return nil
  1020  		})
  1021  	}
  1022  
  1023  	if err := eg.Wait(); err != nil {
  1024  		if err == PassNotPropagatedError {
  1025  			return false, nil
  1026  		}
  1027  		return false, err
  1028  	}
  1029  
  1030  	return true, nil
  1031  }
  1032  
  1033  func (r *ReconcilePerconaXtraDBCluster) updateProxyUser(cr *api.PerconaXtraDBCluster, internalSecrets *corev1.Secret, user *users.SysUser) error {
  1034  	if user == nil {
  1035  		return nil
  1036  	}
  1037  
  1038  	for i := 0; i < int(cr.Spec.ProxySQL.Size); i++ {
  1039  		um, err := users.NewManager(cr.Name+"-proxysql-"+strconv.Itoa(i)+"."+cr.Name+"-proxysql-unready."+cr.Namespace+":6032", users.ProxyAdmin, string(internalSecrets.Data[users.ProxyAdmin]), cr.Spec.PXC.ReadinessProbes.TimeoutSeconds)
  1040  		if err != nil {
  1041  			return errors.Wrap(err, "new users manager")
  1042  		}
  1043  		defer um.Close()
  1044  		err = um.UpdateProxyUser(user)
  1045  		if err != nil {
  1046  			return errors.Wrap(err, "update proxy users")
  1047  		}
  1048  	}
  1049  	return nil
  1050  }
  1051  
  1052  func (r *ReconcilePerconaXtraDBCluster) grantMonitorUserPrivilege(ctx context.Context, cr *api.PerconaXtraDBCluster, internalSysSecretObj *corev1.Secret, um *users.Manager) error {
  1053  	log := logf.FromContext(ctx)
  1054  
  1055  	annotationName := "grant-for-1.10.0-system-privilege"
  1056  	if internalSysSecretObj.Annotations[annotationName] == "done" {
  1057  		return nil
  1058  	}
  1059  
  1060  	if err := um.Update1100MonitorUserPrivilege(); err != nil {
  1061  		return errors.Wrap(err, "grant system user privilege")
  1062  	}
  1063  
  1064  	if internalSysSecretObj.Annotations == nil {
  1065  		internalSysSecretObj.Annotations = make(map[string]string)
  1066  	}
  1067  
  1068  	internalSysSecretObj.Annotations[annotationName] = "done"
  1069  	err := r.client.Update(context.TODO(), internalSysSecretObj)
  1070  	if err != nil {
  1071  		return errors.Wrap(err, "update internal sys users secret annotation")
  1072  	}
  1073  
  1074  	log.Info("monitor user privileges granted")
  1075  	return nil
  1076  }
  1077  
  1078  func getUserManager(cr *api.PerconaXtraDBCluster, secrets *corev1.Secret) (*users.Manager, error) {
  1079  	pxcUser := users.Root
  1080  	pxcPass := string(secrets.Data[users.Root])
  1081  	if _, ok := secrets.Data[users.Operator]; ok {
  1082  		pxcUser = users.Operator
  1083  		pxcPass = string(secrets.Data[users.Operator])
  1084  	}
  1085  
  1086  	addr := cr.Name + "-pxc-unready." + cr.Namespace + ":3306"
  1087  	hasKey, err := cr.ConfigHasKey("mysqld", "proxy_protocol_networks")
  1088  	if err != nil {
  1089  		return nil, errors.Wrap(err, "check if congfig has proxy_protocol_networks key")
  1090  	}
  1091  	if hasKey {
  1092  		addr = cr.Name + "-pxc-unready." + cr.Namespace + ":33062"
  1093  	}
  1094  
  1095  	um, err := users.NewManager(addr, pxcUser, pxcPass, cr.Spec.PXC.ReadinessProbes.TimeoutSeconds)
  1096  	if err != nil {
  1097  		return nil, errors.Wrap(err, "new users manager")
  1098  	}
  1099  
  1100  	return &um, nil
  1101  }
  1102  
  1103  func (r *ReconcilePerconaXtraDBCluster) updateUserPassExpirationPolicy(ctx context.Context, cr *api.PerconaXtraDBCluster, internalSecrets *corev1.Secret, user *users.SysUser) error {
  1104  	log := logf.FromContext(ctx)
  1105  
  1106  	annotationName := "pass-expire-policy-for-1.13.0-user-" + user.Name
  1107  	if internalSecrets.Annotations[annotationName] == "done" {
  1108  		return nil
  1109  	}
  1110  
  1111  	if cr.CompareVersionWith("1.13.0") >= 0 {
  1112  		um, err := getUserManager(cr, internalSecrets)
  1113  		if err != nil {
  1114  			return err
  1115  		}
  1116  
  1117  		if err := um.UpdatePassExpirationPolicy(user); err != nil {
  1118  			return errors.Wrapf(err, "update %s user password expiration policy", user.Name)
  1119  		}
  1120  
  1121  		if internalSecrets.Annotations == nil {
  1122  			internalSecrets.Annotations = make(map[string]string)
  1123  		}
  1124  
  1125  		internalSecrets.Annotations[annotationName] = "done"
  1126  		err = r.client.Update(ctx, internalSecrets)
  1127  		if err != nil {
  1128  			return errors.Wrap(err, "update internal sys users secret annotation")
  1129  		}
  1130  
  1131  		log.Info("Password expiration policy updated", "user", user.Name)
  1132  		return nil
  1133  	}
  1134  
  1135  	return nil
  1136  }
  1137  
  1138  func (r *ReconcilePerconaXtraDBCluster) invalidPasswordApplied(status api.PerconaXtraDBClusterStatus) bool {
  1139  	if len(status.Messages) == 0 {
  1140  		return false
  1141  	}
  1142  
  1143  	if strings.Contains(status.Messages[0], "password does not satisfy the current policy") {
  1144  		return true
  1145  	}
  1146  
  1147  	return false
  1148  }
  1149  
  1150  func (r *ReconcilePerconaXtraDBCluster) updateMySQLInitFile(ctx context.Context, cr *api.PerconaXtraDBCluster, internalSecret *corev1.Secret, user *users.SysUser) error {
  1151  	log := logf.FromContext(ctx)
  1152  
  1153  	secret := &corev1.Secret{
  1154  		ObjectMeta: metav1.ObjectMeta{
  1155  			Name:      cr.Name + "-mysql-init",
  1156  			Namespace: cr.Namespace,
  1157  		},
  1158  	}
  1159  	data := map[string][]byte{
  1160  		"init.sql": []byte("SET SESSION wsrep_on=OFF;\nSET SESSION sql_log_bin=0;\n"),
  1161  	}
  1162  	if err := r.client.Get(ctx, client.ObjectKeyFromObject(secret), secret); err == nil {
  1163  		data = secret.Data
  1164  	}
  1165  
  1166  	statements := make([]string, 0)
  1167  	for _, host := range user.Hosts {
  1168  		statements = append(statements, fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s';\n", user.Name, host, user.Pass))
  1169  	}
  1170  
  1171  	opResult, err := controllerutil.CreateOrUpdate(ctx, r.client, secret, func() error {
  1172  		data["init.sql"] = append(data["init.sql"], []byte(strings.Join(statements, ""))...)
  1173  		secret.Data = data
  1174  		return nil
  1175  	})
  1176  
  1177  	log.Info(fmt.Sprintf("MySQL init secret %s", opResult), "secret", secret.Name, "user", user.Name)
  1178  
  1179  	return err
  1180  }