mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-03 06:11:46 +02:00
first steps for pw rotation
This commit is contained in:
@@ -37,8 +37,10 @@ type OperatorConfigurationList struct {
|
||||
|
||||
// PostgresUsersConfiguration defines the system users of Postgres.
|
||||
type PostgresUsersConfiguration struct {
|
||||
SuperUsername string `json:"super_username,omitempty"`
|
||||
ReplicationUsername string `json:"replication_username,omitempty"`
|
||||
SuperUsername string `json:"super_username,omitempty"`
|
||||
ReplicationUsername string `json:"replication_username,omitempty"`
|
||||
EnablePasswordRotation bool `json:"enable_password_rotation,omitempty"`
|
||||
PasswordRotationInterval Duration `json:"password_rotation_interval,omitempty"`
|
||||
}
|
||||
|
||||
// MajorVersionUpgradeConfiguration defines how to execute major version upgrades of Postgres.
|
||||
|
||||
+36
-12
@@ -17,15 +17,39 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
getUserSQL = `SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.rolinherit,
|
||||
a.rolcreaterole, a.rolcreatedb, a.rolcanlogin, s.setconfig,
|
||||
ARRAY(SELECT b.rolname
|
||||
FROM pg_catalog.pg_auth_members m
|
||||
JOIN pg_catalog.pg_authid b ON (m.roleid = b.oid)
|
||||
WHERE m.member = a.oid) as memberof
|
||||
FROM pg_catalog.pg_authid a LEFT JOIN pg_db_role_setting s ON (a.oid = s.setrole AND s.setdatabase = 0::oid)
|
||||
WHERE a.rolname = ANY($1)
|
||||
ORDER BY 1;`
|
||||
getUserSQL = `WITH dbowners AS (
|
||||
SELECT DISTINCT pg_catalog.pg_get_userbyid(datdba) AS owner
|
||||
FROM pg_database
|
||||
WHERE datname NOT IN ('postgres', 'template0', 'template1')
|
||||
), roles AS (
|
||||
SELECT a.rolname, COALESCE(a.rolpassword, '') AS rolpassword, a.rolsuper, a.rolinherit,
|
||||
a.rolcreaterole, a.rolcreatedb, a.rolcanlogin, s.setconfig,
|
||||
ARRAY(SELECT b.rolname
|
||||
FROM pg_catalog.pg_auth_members m
|
||||
JOIN pg_catalog.pg_authid b ON (m.roleid = b.oid)
|
||||
WHERE m.member = a.oid) as memberof
|
||||
FROM pg_catalog.pg_authid a
|
||||
LEFT JOIN pg_catalog.pg_db_role_setting s ON (a.oid = s.setrole AND s.setdatabase = 0::oid)
|
||||
WHERE a.rolname = ANY($1)
|
||||
ORDER BY 1
|
||||
)
|
||||
SELECT r.rolname, r.rolpassword, r.rolsuper, r.rolinherit,
|
||||
r.rolcreaterole, r.rolcreatedb, r.rolcanlogin, r.setconfig,
|
||||
r.memberof,
|
||||
o.owner IS NOT NULL OR r.rolname LIKE '%_owner' AS is_owner
|
||||
FROM roles r
|
||||
LEFT JOIN dbowners o ON o.owner = r.rolname OR o.owner = ANY (r.memberof)
|
||||
ORDER BY 1;`
|
||||
|
||||
/*`SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.rolinherit,
|
||||
a.rolcreaterole, a.rolcreatedb, a.rolcanlogin, s.setconfig,
|
||||
ARRAY(SELECT b.rolname
|
||||
FROM pg_catalog.pg_auth_members m
|
||||
JOIN pg_catalog.pg_authid b ON (m.roleid = b.oid)
|
||||
WHERE m.member = a.oid) as memberof
|
||||
FROM pg_catalog.pg_authid a LEFT JOIN pg_db_role_setting s ON (a.oid = s.setrole AND s.setdatabase = 0::oid)
|
||||
WHERE a.rolname = ANY($1)
|
||||
ORDER BY 1;`*/
|
||||
|
||||
getDatabasesSQL = `SELECT datname, pg_get_userbyid(datdba) AS owner FROM pg_database;`
|
||||
getSchemasSQL = `SELECT n.nspname AS dbschema FROM pg_catalog.pg_namespace n
|
||||
@@ -198,10 +222,10 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
|
||||
rolname, rolpassword string
|
||||
rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool
|
||||
roloptions, memberof []string
|
||||
roldeleted bool
|
||||
rolowner, roldeleted bool
|
||||
)
|
||||
err := rows.Scan(&rolname, &rolpassword, &rolsuper, &rolinherit,
|
||||
&rolcreaterole, &rolcreatedb, &rolcanlogin, pq.Array(&roloptions), pq.Array(&memberof))
|
||||
&rolcreaterole, &rolcreatedb, &rolcanlogin, pq.Array(&roloptions), pq.Array(&memberof), &rolowner)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when processing user rows: %v", err)
|
||||
}
|
||||
@@ -221,7 +245,7 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
|
||||
roldeleted = true
|
||||
}
|
||||
|
||||
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters, Deleted: roldeleted}
|
||||
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters, IsOwner: rolowner, Deleted: roldeleted}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
|
||||
+40
-7
@@ -613,8 +613,9 @@ func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration(pod *v1.Pod, patroniC
|
||||
|
||||
func (c *Cluster) syncSecrets() error {
|
||||
var (
|
||||
err error
|
||||
secret *v1.Secret
|
||||
err error
|
||||
secret *v1.Secret
|
||||
nextRotationDate time.Time
|
||||
)
|
||||
c.logger.Info("syncing secrets")
|
||||
c.setProcessName("syncing secrets")
|
||||
@@ -631,11 +632,12 @@ func (c *Cluster) syncSecrets() error {
|
||||
if secret, err = c.KubeClient.Secrets(secretSpec.Namespace).Get(context.TODO(), secretSpec.Name, metav1.GetOptions{}); err != nil {
|
||||
return fmt.Errorf("could not get current secret: %v", err)
|
||||
}
|
||||
if secretUsername != string(secret.Data["username"]) {
|
||||
username := string(secret.Data["username"])
|
||||
if secretUsername != username {
|
||||
c.logger.Errorf("secret %s does not contain the role %s", secretSpec.Name, secretUsername)
|
||||
continue
|
||||
}
|
||||
c.Secrets[secret.UID] = secret
|
||||
|
||||
c.logger.Debugf("secret %s already exists, fetching its password", util.NameFromMeta(secret.ObjectMeta))
|
||||
if secretUsername == c.systemUsers[constants.SuperuserKeyName].Name {
|
||||
secretUsername = constants.SuperuserKeyName
|
||||
@@ -647,10 +649,41 @@ func (c *Cluster) syncSecrets() error {
|
||||
userMap = c.pgUsers
|
||||
}
|
||||
pwdUser := userMap[secretUsername]
|
||||
// if this secret belongs to the infrastructure role and the password has changed - replace it in the secret
|
||||
if pwdUser.Password != string(secret.Data["password"]) &&
|
||||
pwdUser.Origin == spec.RoleOriginInfrastructure {
|
||||
|
||||
// if password rotation is enabled update password and username if rotation interval has been passed
|
||||
if c.OpConfig.EnablePasswordRotation && pwdUser.Origin != spec.RoleOriginInfrastructure && !pwdUser.IsOwner { // || c.Spec.InPlacePasswordRotation[secretUsername] {
|
||||
err = json.Unmarshal(secret.Data["nextRotation"], &nextRotationDate)
|
||||
if err != nil {
|
||||
c.logger.Warningf("could not read rotation date of secret %s", secretSpec.Name)
|
||||
nextRotationDate = time.Now()
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
if currentTime.After(nextRotationDate) {
|
||||
//if !c.Spec.InPlacePasswordRotation[secretUsername] {
|
||||
newRotationUsername := secretUsername + "_" + currentTime.Format("060102")
|
||||
pwdUser.Name = newRotationUsername
|
||||
pwdUser.MemberOf = []string{secretUsername}
|
||||
pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(spec.PgUserMap{}, map[string]spec.PgUser{newRotationUsername: pwdUser})
|
||||
if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil {
|
||||
return fmt.Errorf("error executing sync statements: %v", err)
|
||||
}
|
||||
secret.Data["username"] = []byte(newRotationUsername)
|
||||
//}
|
||||
secret.Data["password"] = []byte(util.RandomPassword(constants.PasswordLength))
|
||||
secret.Data["nextRotation"] = []byte(currentTime.Add(c.OpConfig.PasswordRotationInterval).Format("2006-01-02 15:04:05"))
|
||||
|
||||
c.logger.Debugf("updating the secret %s due to password rotation", secretSpec.Name)
|
||||
if _, err = c.KubeClient.Secrets(secretSpec.Namespace).Update(context.TODO(), secretSpec, metav1.UpdateOptions{}); err != nil {
|
||||
return fmt.Errorf("could not update secret %q: %v", secretUsername, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Secrets[secret.UID] = secret
|
||||
|
||||
// if this secret belongs to the infrastructure role and the password has changed - replace it in the secret
|
||||
if pwdUser.Password != string(secret.Data["password"]) && pwdUser.Origin == spec.RoleOriginInfrastructure {
|
||||
c.logger.Debugf("updating the secret %s from the infrastructure roles", secretSpec.Name)
|
||||
if _, err = c.KubeClient.Secrets(secretSpec.Namespace).Update(context.TODO(), secretSpec, metav1.UpdateOptions{}); err != nil {
|
||||
return fmt.Errorf("could not update infrastructure role secret for role %q: %v", secretUsername, err)
|
||||
|
||||
@@ -54,6 +54,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
// user config
|
||||
result.SuperUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.SuperUsername, "postgres")
|
||||
result.ReplicationUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.ReplicationUsername, "standby")
|
||||
result.EnablePasswordRotation = fromCRD.PostgresUsersConfiguration.EnablePasswordRotation
|
||||
result.PasswordRotationInterval = util.CoalesceDuration(time.Duration(fromCRD.PostgresUsersConfiguration.PasswordRotationInterval), "90d")
|
||||
|
||||
// major version upgrade config
|
||||
result.MajorVersionUpgradeMode = util.Coalesce(fromCRD.MajorVersionUpgrade.MajorVersionUpgradeMode, "off")
|
||||
|
||||
@@ -55,6 +55,7 @@ type PgUser struct {
|
||||
MemberOf []string `yaml:"inrole"`
|
||||
Parameters map[string]string `yaml:"db_parameters"`
|
||||
AdminRole string `yaml:"admin_role"`
|
||||
IsOwner bool `yaml:"is_owner"`
|
||||
Deleted bool `yaml:"deleted"`
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ type Auth struct {
|
||||
InfrastructureRolesDefs string `name:"infrastructure_roles_secrets"`
|
||||
SuperUsername string `name:"super_username" default:"postgres"`
|
||||
ReplicationUsername string `name:"replication_username" default:"standby"`
|
||||
EnablePasswordRotation bool `name:"enable_password_rotation" default:"false"`
|
||||
PasswordRotationInterval time.Duration `name:"password_rotation_interval" default:"90d"`
|
||||
}
|
||||
|
||||
// Scalyr holds the configuration for the Scalyr Agent sidecar for log shipping:
|
||||
|
||||
Reference in New Issue
Block a user