Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions pkg/cluster/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@ func (c *Cluster) rotatePasswordInSecret(
err error
nextRotationDate time.Time
nextRotationDateStr string
expectedUsername string
rotationModeChanged bool
updateSecretMsg string
)

Expand All @@ -962,17 +964,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 {
Expand All @@ -986,7 +1003,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(),
Expand All @@ -1003,10 +1020,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)
Expand Down
31 changes: 30 additions & 1 deletion pkg/cluster/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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{
Expand Down Expand Up @@ -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)
}
}