copy rolconfig during password rotation (#2183)
* copy rolconfig during password rotation Co-authored-by: idanovinda <idanovinda@gmail.com>
This commit is contained in:
parent
63c9f916a6
commit
4741b3f734
|
|
@ -932,7 +932,8 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
"AdminRole": "",
|
"AdminRole": "",
|
||||||
"Origin": 2,
|
"Origin": 2,
|
||||||
"IsDbOwner": False,
|
"IsDbOwner": False,
|
||||||
"Deleted": False
|
"Deleted": False,
|
||||||
|
"Rotated": False
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
|
|
@ -1472,9 +1473,9 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
# create fake rotation users that should be removed by operator
|
# create fake rotation users that should be removed by operator
|
||||||
# but have one that would still fit into the retention period
|
# but have one that would still fit into the retention period
|
||||||
create_fake_rotation_user = """
|
create_fake_rotation_user = """
|
||||||
CREATE ROLE foo_user201031 IN ROLE foo_user;
|
CREATE USER foo_user201031 IN ROLE foo_user;
|
||||||
CREATE ROLE foo_user211031 IN ROLE foo_user;
|
CREATE USER foo_user211031 IN ROLE foo_user;
|
||||||
CREATE ROLE foo_user"""+(today-timedelta(days=40)).strftime("%y%m%d")+""" IN ROLE foo_user;
|
CREATE USER foo_user"""+(today-timedelta(days=40)).strftime("%y%m%d")+""" IN ROLE foo_user;
|
||||||
"""
|
"""
|
||||||
self.query_database(leader.metadata.name, "postgres", create_fake_rotation_user)
|
self.query_database(leader.metadata.name, "postgres", create_fake_rotation_user)
|
||||||
|
|
||||||
|
|
@ -1491,6 +1492,12 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
namespace="default",
|
namespace="default",
|
||||||
body=secret_fake_rotation)
|
body=secret_fake_rotation)
|
||||||
|
|
||||||
|
# update rolconfig for foo_user that will be copied for new rotation user
|
||||||
|
alter_foo_user_search_path = """
|
||||||
|
ALTER ROLE foo_user SET search_path TO data;
|
||||||
|
"""
|
||||||
|
self.query_database(leader.metadata.name, "postgres", alter_foo_user_search_path)
|
||||||
|
|
||||||
# enable password rotation for all other users (foo_user)
|
# enable password rotation for all other users (foo_user)
|
||||||
# this will force a sync of secrets for further assertions
|
# this will force a sync of secrets for further assertions
|
||||||
enable_password_rotation = {
|
enable_password_rotation = {
|
||||||
|
|
@ -1526,6 +1533,18 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 3,
|
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 3,
|
||||||
"Found incorrect number of rotation users", 10, 5)
|
"Found incorrect number of rotation users", 10, 5)
|
||||||
|
|
||||||
|
# check if rolconfig was passed from foo_user to foo_user+today
|
||||||
|
# and that no foo_user has been deprecated (can still login)
|
||||||
|
user_query = """
|
||||||
|
SELECT rolname
|
||||||
|
FROM pg_catalog.pg_roles
|
||||||
|
WHERE rolname LIKE 'foo_user%'
|
||||||
|
AND rolconfig = ARRAY['search_path=data']::text[]
|
||||||
|
AND rolcanlogin;
|
||||||
|
"""
|
||||||
|
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
|
||||||
|
"Rolconfig not applied to new rotation user", 10, 5)
|
||||||
|
|
||||||
# test that rotation_user can connect to the database
|
# test that rotation_user can connect to the database
|
||||||
self.eventuallyEqual(lambda: len(self.query_database_with_user(leader.metadata.name, "postgres", "SELECT 1", "foo_user")), 1,
|
self.eventuallyEqual(lambda: len(self.query_database_with_user(leader.metadata.name, "postgres", "SELECT 1", "foo_user")), 1,
|
||||||
"Could not connect to the database with rotation user {}".format(rotation_user), 10, 5)
|
"Could not connect to the database with rotation user {}".format(rotation_user), 10, 5)
|
||||||
|
|
@ -1559,7 +1578,7 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
WHERE rolname LIKE 'foo_user%';
|
WHERE rolname LIKE 'foo_user%';
|
||||||
"""
|
"""
|
||||||
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
|
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
|
||||||
"Found incorrect number of rotation users", 10, 5)
|
"Found incorrect number of rotation users after disabling password rotation", 10, 5)
|
||||||
|
|
||||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||||
def test_rolling_update_flag(self):
|
def test_rolling_update_flag(self):
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ func (c *Cluster) cleanupRotatedUsers(rotatedUsers []string, db *sql.DB) error {
|
||||||
retentionDate := time.Now().AddDate(0, 0, int(retenionDays)*-1)
|
retentionDate := time.Now().AddDate(0, 0, int(retenionDays)*-1)
|
||||||
|
|
||||||
for rotatedUser, dateSuffix := range extraUsers {
|
for rotatedUser, dateSuffix := range extraUsers {
|
||||||
userCreationDate, err := time.Parse("060102", dateSuffix)
|
userCreationDate, err := time.Parse(constants.RotationUserDateFormat, dateSuffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Errorf("could not parse creation date suffix of user %q: %v", rotatedUser, err)
|
c.logger.Errorf("could not parse creation date suffix of user %q: %v", rotatedUser, err)
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -656,7 +656,6 @@ func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration(pod *v1.Pod, effectiv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) syncSecrets() error {
|
func (c *Cluster) syncSecrets() error {
|
||||||
|
|
||||||
c.logger.Info("syncing secrets")
|
c.logger.Info("syncing secrets")
|
||||||
c.setProcessName("syncing secrets")
|
c.setProcessName("syncing secrets")
|
||||||
generatedSecrets := c.generateUserSecrets()
|
generatedSecrets := c.generateUserSecrets()
|
||||||
|
|
@ -792,6 +791,7 @@ func (c *Cluster) updateSecret(
|
||||||
pwdUser.Password = string(secret.Data["password"])
|
pwdUser.Password = string(secret.Data["password"])
|
||||||
// update membership if we deal with a rotation user
|
// update membership if we deal with a rotation user
|
||||||
if secretUsername != pwdUser.Name {
|
if secretUsername != pwdUser.Name {
|
||||||
|
pwdUser.Rotated = true
|
||||||
pwdUser.MemberOf = []string{secretUsername}
|
pwdUser.MemberOf = []string{secretUsername}
|
||||||
}
|
}
|
||||||
userMap[userKey] = pwdUser
|
userMap[userKey] = pwdUser
|
||||||
|
|
@ -842,7 +842,7 @@ func (c *Cluster) rotatePasswordInSecret(
|
||||||
if currentTime.After(nextRotationDate) {
|
if currentTime.After(nextRotationDate) {
|
||||||
// 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 !util.SliceContains(c.Spec.UsersWithInPlaceSecretRotation, secretUsername) {
|
if !util.SliceContains(c.Spec.UsersWithInPlaceSecretRotation, secretUsername) {
|
||||||
rotationUsername := fmt.Sprintf("%s%s", secretUsername, currentTime.Format("060102"))
|
rotationUsername := fmt.Sprintf("%s%s", secretUsername, currentTime.Format(constants.RotationUserDateFormat))
|
||||||
secret.Data["username"] = []byte(rotationUsername)
|
secret.Data["username"] = []byte(rotationUsername)
|
||||||
c.logger.Infof("updating username in secret %s and creating rotation user %s in the database", secretName, rotationUsername)
|
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
|
||||||
|
|
@ -924,6 +924,12 @@ func (c *Cluster) syncRoles() (err error) {
|
||||||
for _, u := range c.pgUsers {
|
for _, u := range c.pgUsers {
|
||||||
pgRole := u.Name
|
pgRole := u.Name
|
||||||
userNames = append(userNames, pgRole)
|
userNames = append(userNames, pgRole)
|
||||||
|
|
||||||
|
// when a rotation happened add group role to query its rolconfig
|
||||||
|
if u.Rotated {
|
||||||
|
userNames = append(userNames, u.MemberOf[0])
|
||||||
|
}
|
||||||
|
|
||||||
// add team member role name with rename suffix in case we need to rename it back
|
// add team member role name with rename suffix in case we need to rename it back
|
||||||
if u.Origin == spec.RoleOriginTeamsAPI && c.OpConfig.EnableTeamMemberDeprecation {
|
if u.Origin == spec.RoleOriginTeamsAPI && c.OpConfig.EnableTeamMemberDeprecation {
|
||||||
deletedUsers[pgRole+c.OpConfig.RoleDeletionSuffix] = pgRole
|
deletedUsers[pgRole+c.OpConfig.RoleDeletionSuffix] = pgRole
|
||||||
|
|
@ -950,9 +956,21 @@ func (c *Cluster) syncRoles() (err error) {
|
||||||
return fmt.Errorf("error getting users from the database: %v", err)
|
return fmt.Errorf("error getting users from the database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBUSERS:
|
||||||
|
for _, dbUser := range dbUsers {
|
||||||
|
// copy rolconfig to rotation users
|
||||||
|
for pgUserName, pgUser := range c.pgUsers {
|
||||||
|
if pgUser.Rotated && pgUser.MemberOf[0] == dbUser.Name {
|
||||||
|
pgUser.Parameters = dbUser.Parameters
|
||||||
|
c.pgUsers[pgUserName] = pgUser
|
||||||
|
// remove group role from dbUsers to not count as deleted role
|
||||||
|
delete(dbUsers, dbUser.Name)
|
||||||
|
continue DBUSERS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update pgUsers where a deleted role was found
|
// update pgUsers where a deleted role was found
|
||||||
// so that they are skipped in ProduceSyncRequests
|
// so that they are skipped in ProduceSyncRequests
|
||||||
for _, dbUser := range dbUsers {
|
|
||||||
originalUsername, foundDeletedUser := deletedUsers[dbUser.Name]
|
originalUsername, foundDeletedUser := deletedUsers[dbUser.Name]
|
||||||
// check if original user does not exist in dbUsers
|
// check if original user does not exist in dbUsers
|
||||||
_, originalUserAlreadyExists := dbUsers[originalUsername]
|
_, originalUserAlreadyExists := dbUsers[originalUsername]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/zalando/postgres-operator/pkg/spec"
|
"github.com/zalando/postgres-operator/pkg/spec"
|
||||||
"github.com/zalando/postgres-operator/pkg/util"
|
"github.com/zalando/postgres-operator/pkg/util"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/config"
|
"github.com/zalando/postgres-operator/pkg/util/config"
|
||||||
|
"github.com/zalando/postgres-operator/pkg/util/constants"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/patroni"
|
"github.com/zalando/postgres-operator/pkg/util/patroni"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
@ -593,7 +594,7 @@ func TestUpdateSecret(t *testing.T) {
|
||||||
t.Errorf("%s: username differs in updated secret: expected %s, got %s", testName, username, secretUsername)
|
t.Errorf("%s: username differs in updated secret: expected %s, got %s", testName, username, secretUsername)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rotatedUsername := username + dayAfterTomorrow.Format("060102")
|
rotatedUsername := username + dayAfterTomorrow.Format(constants.RotationUserDateFormat)
|
||||||
if secretUsername != rotatedUsername {
|
if secretUsername != rotatedUsername {
|
||||||
t.Errorf("%s: updated secret does not contain correct username: expected %s, got %s", testName, rotatedUsername, secretUsername)
|
t.Errorf("%s: updated secret does not contain correct username: expected %s, got %s", testName, rotatedUsername, secretUsername)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ type PgUser struct {
|
||||||
AdminRole string `yaml:"admin_role"`
|
AdminRole string `yaml:"admin_role"`
|
||||||
IsDbOwner bool `yaml:"is_db_owner"`
|
IsDbOwner bool `yaml:"is_db_owner"`
|
||||||
Deleted bool `yaml:"deleted"`
|
Deleted bool `yaml:"deleted"`
|
||||||
|
Rotated bool `yaml:"rotated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *PgUser) Valid() bool {
|
func (user *PgUser) Valid() bool {
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,5 @@ const (
|
||||||
WriterRoleNameSuffix = "_writer"
|
WriterRoleNameSuffix = "_writer"
|
||||||
UserRoleNameSuffix = "_user"
|
UserRoleNameSuffix = "_user"
|
||||||
DefaultSearchPath = "\"$user\""
|
DefaultSearchPath = "\"$user\""
|
||||||
|
RotationUserDateFormat = "060102"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue