From 3fb3b3409463fab52cadfde5dea0d261d1f534c9 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Thu, 22 Feb 2024 10:26:13 +0100 Subject: [PATCH] change username in secret when switching rotation mode (#2549) --- pkg/cluster/sync.go | 33 ++++++++++++++++++++++++++------- pkg/cluster/sync_test.go | 31 ++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 19aab1e51..d5f48ac5c 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -949,6 +949,8 @@ func (c *Cluster) rotatePasswordInSecret( err error nextRotationDate time.Time nextRotationDateStr string + expectedUsername string + rotationModeChanged bool updateSecretMsg string ) @@ -969,17 +971,32 @@ func (c *Cluster) rotatePasswordInSecret( nextRotationDate = currentRotationDate } + // set username and check if it differs from current value in secret + currentUsername := string(secret.Data["username"]) + if !slices.Contains(c.Spec.UsersWithInPlaceSecretRotation, secretUsername) { + expectedUsername = fmt.Sprintf("%s%s", secretUsername, currentTime.Format(constants.RotationUserDateFormat)) + } else { + expectedUsername = secretUsername + } + + // when changing to in-place rotation update secret immediatly + // if currentUsername is longer we know it has a date suffix + // the other way around we can wait until the next rotation date + if len(currentUsername) > len(expectedUsername) { + rotationModeChanged = true + c.logger.Infof("updating secret %s after switching to in-place rotation mode for username: %s", secretName, string(secret.Data["username"])) + } + // update password and next rotation date if configured interval has passed - if currentTime.After(nextRotationDate) { + if currentTime.After(nextRotationDate) || rotationModeChanged { // create rotation user if role is not listed for in-place password update if !slices.Contains(c.Spec.UsersWithInPlaceSecretRotation, secretUsername) { - rotationUsername := fmt.Sprintf("%s%s", secretUsername, currentTime.Format(constants.RotationUserDateFormat)) - secret.Data["username"] = []byte(rotationUsername) - c.logger.Infof("updating username in secret %s and creating rotation user %s in the database", secretName, rotationUsername) + secret.Data["username"] = []byte(expectedUsername) + c.logger.Infof("updating username in secret %s and creating rotation user %s in the database", secretName, expectedUsername) // whenever there is a rotation, check if old rotation users can be deleted *retentionUsers = append(*retentionUsers, secretUsername) } else { - // when passwords of system users are rotated in place, pods have to be replaced + // when passwords of system users are rotated in-place, pods have to be replaced if roleOrigin == spec.RoleOriginSystem { pods, err := c.listPods() if err != nil { @@ -993,7 +1010,7 @@ func (c *Cluster) rotatePasswordInSecret( } } - // when password of connection pooler is rotated in place, pooler pods have to be replaced + // when password of connection pooler is rotated in-place, pooler pods have to be replaced if roleOrigin == spec.RoleOriginConnectionPooler { listOptions := metav1.ListOptions{ LabelSelector: c.poolerLabelsSet(true).String(), @@ -1010,10 +1027,12 @@ func (c *Cluster) rotatePasswordInSecret( } } - // when password of stream user is rotated in place, it should trigger rolling update in FES deployment + // when password of stream user is rotated in-place, it should trigger rolling update in FES deployment if roleOrigin == spec.RoleOriginStream { c.logger.Warnf("password in secret of stream user %s changed", constants.EventStreamSourceSlotPrefix+constants.UserRoleNameSuffix) } + + secret.Data["username"] = []byte(secretUsername) } secret.Data["password"] = []byte(util.RandomPassword(constants.PasswordLength)) secret.Data["nextRotation"] = []byte(nextRotationDateStr) diff --git a/pkg/cluster/sync_test.go b/pkg/cluster/sync_test.go index aea81e0c3..46d1be5b7 100644 --- a/pkg/cluster/sync_test.go +++ b/pkg/cluster/sync_test.go @@ -624,6 +624,7 @@ func TestUpdateSecret(t *testing.T) { namespace := "default" dbname := "app" dbowner := "appowner" + appUser := "foo" secretTemplate := config.StringTemplate("{username}.{cluster}.credentials") retentionUsers := make([]string, 0) @@ -635,7 +636,7 @@ func TestUpdateSecret(t *testing.T) { }, Spec: acidv1.PostgresSpec{ Databases: map[string]string{dbname: dbowner}, - Users: map[string]acidv1.UserFlags{"foo": {}, "bar": {}, dbowner: {}}, + Users: map[string]acidv1.UserFlags{appUser: {}, "bar": {}, dbowner: {}}, UsersIgnoringSecretRotation: []string{"bar"}, UsersWithInPlaceSecretRotation: []string{dbowner}, Streams: []acidv1.Stream{ @@ -744,4 +745,32 @@ func TestUpdateSecret(t *testing.T) { } } } + + // switch rotation for foo to in-place + inPlaceRotationUsers := []string{dbowner, appUser} + cluster.Spec.UsersWithInPlaceSecretRotation = inPlaceRotationUsers + cluster.initUsers() + cluster.syncSecrets() + updatedSecret, err := cluster.KubeClient.Secrets(namespace).Get(context.TODO(), cluster.credentialSecretName(appUser), metav1.GetOptions{}) + assert.NoError(t, err) + + // username in secret should be switched to original user + currentUsername := string(updatedSecret.Data["username"]) + if currentUsername != appUser { + t.Errorf("%s: updated secret does not contain correct username: expected %s, got %s", testName, appUser, currentUsername) + } + + // switch rotation back to rotation user + inPlaceRotationUsers = []string{dbowner} + cluster.Spec.UsersWithInPlaceSecretRotation = inPlaceRotationUsers + cluster.initUsers() + cluster.syncSecrets() + updatedSecret, err = cluster.KubeClient.Secrets(namespace).Get(context.TODO(), cluster.credentialSecretName(appUser), metav1.GetOptions{}) + assert.NoError(t, err) + + // username in secret will only be switched after next rotation date is passed + currentUsername = string(updatedSecret.Data["username"]) + if currentUsername != appUser { + t.Errorf("%s: updated secret does not contain expected username: expected %s, got %s", testName, appUser, currentUsername) + } }