change username in secret when switching rotation mode (#2549)
This commit is contained in:
parent
e34f19be01
commit
3fb3b34094
|
|
@ -949,6 +949,8 @@ func (c *Cluster) rotatePasswordInSecret(
|
||||||
err error
|
err error
|
||||||
nextRotationDate time.Time
|
nextRotationDate time.Time
|
||||||
nextRotationDateStr string
|
nextRotationDateStr string
|
||||||
|
expectedUsername string
|
||||||
|
rotationModeChanged bool
|
||||||
updateSecretMsg string
|
updateSecretMsg string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -969,17 +971,32 @@ func (c *Cluster) rotatePasswordInSecret(
|
||||||
nextRotationDate = currentRotationDate
|
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
|
// 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
|
// create rotation user if role is not listed for in-place password update
|
||||||
if !slices.Contains(c.Spec.UsersWithInPlaceSecretRotation, secretUsername) {
|
if !slices.Contains(c.Spec.UsersWithInPlaceSecretRotation, secretUsername) {
|
||||||
rotationUsername := fmt.Sprintf("%s%s", secretUsername, currentTime.Format(constants.RotationUserDateFormat))
|
secret.Data["username"] = []byte(expectedUsername)
|
||||||
secret.Data["username"] = []byte(rotationUsername)
|
c.logger.Infof("updating username in secret %s and creating rotation user %s in the database", secretName, expectedUsername)
|
||||||
c.logger.Infof("updating username in secret %s and creating rotation user %s in the database", secretName, rotationUsername)
|
|
||||||
// whenever there is a rotation, check if old rotation users can be deleted
|
// whenever there is a rotation, check if old rotation users can be deleted
|
||||||
*retentionUsers = append(*retentionUsers, secretUsername)
|
*retentionUsers = append(*retentionUsers, secretUsername)
|
||||||
} else {
|
} 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 {
|
if roleOrigin == spec.RoleOriginSystem {
|
||||||
pods, err := c.listPods()
|
pods, err := c.listPods()
|
||||||
if err != nil {
|
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 {
|
if roleOrigin == spec.RoleOriginConnectionPooler {
|
||||||
listOptions := metav1.ListOptions{
|
listOptions := metav1.ListOptions{
|
||||||
LabelSelector: c.poolerLabelsSet(true).String(),
|
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 {
|
if roleOrigin == spec.RoleOriginStream {
|
||||||
c.logger.Warnf("password in secret of stream user %s changed", constants.EventStreamSourceSlotPrefix+constants.UserRoleNameSuffix)
|
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["password"] = []byte(util.RandomPassword(constants.PasswordLength))
|
||||||
secret.Data["nextRotation"] = []byte(nextRotationDateStr)
|
secret.Data["nextRotation"] = []byte(nextRotationDateStr)
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,7 @@ func TestUpdateSecret(t *testing.T) {
|
||||||
namespace := "default"
|
namespace := "default"
|
||||||
dbname := "app"
|
dbname := "app"
|
||||||
dbowner := "appowner"
|
dbowner := "appowner"
|
||||||
|
appUser := "foo"
|
||||||
secretTemplate := config.StringTemplate("{username}.{cluster}.credentials")
|
secretTemplate := config.StringTemplate("{username}.{cluster}.credentials")
|
||||||
retentionUsers := make([]string, 0)
|
retentionUsers := make([]string, 0)
|
||||||
|
|
||||||
|
|
@ -635,7 +636,7 @@ func TestUpdateSecret(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: acidv1.PostgresSpec{
|
Spec: acidv1.PostgresSpec{
|
||||||
Databases: map[string]string{dbname: dbowner},
|
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"},
|
UsersIgnoringSecretRotation: []string{"bar"},
|
||||||
UsersWithInPlaceSecretRotation: []string{dbowner},
|
UsersWithInPlaceSecretRotation: []string{dbowner},
|
||||||
Streams: []acidv1.Stream{
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue