first steps for pw rotation
This commit is contained in:
		
							parent
							
								
									fe340192ca
								
							
						
					
					
						commit
						259acddfa0
					
				|  | @ -122,6 +122,12 @@ spec: | |||
|               users: | ||||
|                 type: object | ||||
|                 properties: | ||||
|                   enable_password_rotation: | ||||
|                     type: boolean | ||||
|                     default: false | ||||
|                   password_rotation_interval: | ||||
|                     type: string | ||||
|                     default: "90d" | ||||
|                   replication_username: | ||||
|                      type: string | ||||
|                      default: standby | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ data: | |||
|   # enable_init_containers: "true" | ||||
|   # enable_lazy_spilo_upgrade: "false" | ||||
|   enable_master_load_balancer: "false" | ||||
|   enable_password_rotation: "true" | ||||
|   enable_pgversion_env_var: "true" | ||||
|   # enable_pod_antiaffinity: "false" | ||||
|   # enable_pod_disruption_budget: "true" | ||||
|  | @ -91,6 +92,7 @@ data: | |||
|   # pam_configuration: | | ||||
|   #  https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees | ||||
|   # pam_role_name: zalandos | ||||
|   password_rotation_interval: 10m | ||||
|   pdb_name_format: "postgres-{cluster}-pdb" | ||||
|   # pod_antiaffinity_topology_key: "kubernetes.io/hostname" | ||||
|   pod_deletion_wait_timeout: 10m | ||||
|  |  | |||
|  | @ -120,6 +120,12 @@ spec: | |||
|               users: | ||||
|                 type: object | ||||
|                 properties: | ||||
|                   enable_password_rotation: | ||||
|                     type: boolean | ||||
|                     default: false | ||||
|                   password_rotation_interval: | ||||
|                     type: string | ||||
|                     default: "90d" | ||||
|                   replication_username: | ||||
|                      type: string | ||||
|                      default: standby | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ spec: | |||
|       serviceAccountName: postgres-operator | ||||
|       containers: | ||||
|       - name: postgres-operator | ||||
|         image: registry.opensource.zalan.do/acid/postgres-operator:v1.7.1 | ||||
|         image: registry.opensource.zalan.do/acid/postgres-operator:v1.7.1-16-gfe340192-dirty | ||||
|         imagePullPolicy: IfNotPresent | ||||
|         resources: | ||||
|           requests: | ||||
|  |  | |||
|  | @ -25,6 +25,8 @@ configuration: | |||
|   #     protocol: TCP | ||||
|   workers: 8 | ||||
|   users: | ||||
|     enable_password_rotation: false | ||||
|     password_rotation_interval: 90d | ||||
|     replication_username: standby | ||||
|     super_username: postgres | ||||
|   major_version_upgrade: | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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:
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue