diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index b801ed331..c4b20c5e0 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -130,8 +130,11 @@ spec: users: type: object properties: - cron_admin_username: - type: string + additional_owner_roles: + type: array + nullable: true + items: + type: string enable_password_rotation: type: boolean default: false @@ -502,6 +505,7 @@ spec: type: string default: - admin + - cron_admin role_deletion_suffix: type: string default: "_deleted" diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index f78a358c6..38e389a8c 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -59,8 +59,10 @@ configGeneral: # parameters describing Postgres users configUsers: - # username used to grant rights to set up and maintain cron jobs to database owners - cron_admin_username: cron_admin + # roles to be granted to database owners + # additional_owner_roles: + # - cron_admin + # enable password rotation for app users that are not database owners enable_password_rotation: false # rotation interval for updating credentials in K8s secrets of app users @@ -346,6 +348,7 @@ configTeamsApi: # List of roles that cannot be overwritten by an application, team or infrastructure role protected_role_names: - admin + - cron_admin # Suffix to add if members are removed from TeamsAPI or PostgresTeam CRD role_deletion_suffix: "_deleted" # role name to grant to team members created from the Teams API diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 0059bd103..136155b8a 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -177,12 +177,14 @@ under the `users` key. Postgres username used for replication between instances. The default is `standby`. -* **cron_admin_username** - Specifies the role that owns rights to set up cron jobs with `pg_cron` - extension inside the `postgres` database. The must be pre-configured in the - docker image. In Spilo this role is called `cron_admin`. This role will - become a member of all database owners so that they can set up cron jobs - e.g. as part of a migration script. Default is `empty`. +* **additional_owner_roles** + Specifies database roles that will become members of all database owners. + Then owners can use `SET ROLE` to obtain privileges of these roles to e.g. + create/update functionality from extensions as part of a migration script. + Note, that roles listed here should be preconfigured in the docker image + and already exist in the database cluster on startup. One such role can be + `cron_admin` which is provided by the Spilo docker image to set up cron + jobs inside the `postgres` database. Default is `empty`. * **enable_password_rotation** For all `LOGIN` roles that are not database owners the operator can rotate @@ -755,7 +757,7 @@ key. * **protected_role_names** List of roles that cannot be overwritten by an application, team or - infrastructure role. The default is `admin`. + infrastructure role. The default list is `admin` and `cron_admin`. * **postgres_superuser_teams** List of teams which members need the superuser role in each PG database diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index a3c033745..faef2eb1b 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -3,6 +3,7 @@ kind: ConfigMap metadata: name: postgres-operator data: + # additional_owner_roles: "cron_admin" # additional_pod_capabilities: "SYS_NICE" # additional_secret_mount: "some-secret-name" # additional_secret_mount_path: "/some/dir" @@ -23,7 +24,6 @@ data: # connection_pooler_schema: "pooler" # connection_pooler_user: "pooler" crd_categories: "all" - # cron_admin_username: "cron_admin" # custom_service_annotations: "keyx:valuez,keya:valuea" # custom_pod_annotations: "keya:valuea,keyb:valueb" db_hosted_zone: db.example.com diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index ff99a6e7b..3c5ad3548 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -128,8 +128,11 @@ spec: users: type: object properties: - cron_admin_username: - type: string + additional_owner_roles: + type: array + nullable: true + items: + type: string enable_password_rotation: type: boolean default: false @@ -500,6 +503,7 @@ spec: type: string default: - admin + - cron_admin role_deletion_suffix: type: string default: "_deleted" diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 28aaabb21..518b4dd49 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -26,12 +26,13 @@ configuration: # protocol: TCP workers: 8 users: + # additional_owner_roles: + # - cron_admin enable_password_rotation: false password_rotation_interval: 90 password_rotation_user_retention: 180 replication_username: standby super_username: postgres - # cron_admin_username: cron_admin major_version_upgrade: major_version_upgrade_mode: "off" # major_version_upgrade_team_allow_list: diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 043c8aaea..fb4678a19 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -1142,8 +1142,14 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "users": { Type: "object", Properties: map[string]apiextv1.JSONSchemaProps{ - "cron_admin_username": { - Type: "string", + "additional_owner_roles": { + Type: "array", + Nullable: true, + Items: &apiextv1.JSONSchemaPropsOrArray{ + Schema: &apiextv1.JSONSchemaProps{ + Type: "string", + }, + }, }, "enable_password_rotation": { Type: "boolean", diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index 7fbd5f9de..d2a0e8969 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -37,12 +37,12 @@ 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"` - CronAdminUsername string `json:"cron_admin_username,omitempty"` - EnablePasswordRotation bool `json:"enable_password_rotation,omitempty"` - PasswordRotationInterval uint32 `json:"password_rotation_interval,omitempty"` - PasswordRotationUserRetention uint32 `json:"password_rotation_user_retention,omitempty"` + SuperUsername string `json:"super_username,omitempty"` + ReplicationUsername string `json:"replication_username,omitempty"` + AddtionalOwnerRoles []string `json:"additional_owner_roles,omitempty"` + EnablePasswordRotation bool `json:"enable_password_rotation,omitempty"` + PasswordRotationInterval uint32 `json:"password_rotation_interval,omitempty"` + PasswordRotationUserRetention uint32 `json:"password_rotation_user_retention,omitempty"` } // MajorVersionUpgradeConfiguration defines how to execute major version upgrades of Postgres. diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index c2298fada..4b791293d 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -401,7 +401,7 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.PostgresUsersConfiguration = in.PostgresUsersConfiguration + in.PostgresUsersConfiguration.DeepCopyInto(&out.PostgresUsersConfiguration) in.MajorVersionUpgrade.DeepCopyInto(&out.MajorVersionUpgrade) in.Kubernetes.DeepCopyInto(&out.Kubernetes) out.PostgresPodResources = in.PostgresPodResources @@ -916,6 +916,11 @@ func (in *PostgresTeamSpec) DeepCopy() *PostgresTeamSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgresUsersConfiguration) DeepCopyInto(out *PostgresUsersConfiguration) { *out = *in + if in.AddtionalOwnerRoles != nil { + in, out := &in.AddtionalOwnerRoles, &out.AddtionalOwnerRoles + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 8ac0ab1ff..6567e6c9c 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -228,7 +228,7 @@ func (c *Cluster) initUsers() error { return fmt.Errorf("could not init human users: %v", err) } - c.initCronAdmin() + c.initAdditionalOwnerRoles() return nil } @@ -1299,36 +1299,29 @@ func (c *Cluster) initRobotUsers() error { return nil } -func (c *Cluster) initCronAdmin() { - cronAdminName := c.OpConfig.CronAdminUsername - if cronAdminName == "" { - return - } - memberOf := make([]string, 0) - for username, pgUser := range c.pgUsers { - if pgUser.IsDbOwner { - memberOf = append(memberOf, username) +func (c *Cluster) initAdditionalOwnerRoles() { + for _, additionalOwner := range c.OpConfig.AddtionalOwnerRoles { + // fetch all database owners the additional should become a member of + memberOf := make([]string, 0) + for username, pgUser := range c.pgUsers { + if pgUser.IsDbOwner { + memberOf = append(memberOf, username) + } } - } - if len(memberOf) > 1 { - namespace := c.Namespace - adminRole := "" - if c.OpConfig.EnableAdminRoleForUsers && cronAdminName != c.OpConfig.TeamAdminRole { - adminRole = c.OpConfig.TeamAdminRole - } - cronAdmin := spec.PgUser{ - Origin: spec.RoleOriginSpilo, - MemberOf: memberOf, - Name: cronAdminName, - Namespace: namespace, - Flags: []string{constants.RoleFlagNoLogin}, - AdminRole: adminRole, - } - if currentRole, present := c.pgUsers[cronAdminName]; present { - c.pgUsers[cronAdminName] = c.resolveNameConflict(¤tRole, &cronAdmin) - } else { - c.pgUsers[cronAdminName] = cronAdmin + if len(memberOf) > 1 { + namespace := c.Namespace + additionalOwnerPgUser := spec.PgUser{ + Origin: spec.RoleOriginSpilo, + MemberOf: memberOf, + Name: additionalOwner, + Namespace: namespace, + } + if currentRole, present := c.pgUsers[additionalOwner]; present { + c.pgUsers[additionalOwner] = c.resolveNameConflict(¤tRole, &additionalOwnerPgUser) + } else { + c.pgUsers[additionalOwner] = additionalOwnerPgUser + } } } } diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 139a0810e..9619403f7 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -101,7 +101,7 @@ type Auth struct { InfrastructureRolesDefs string `name:"infrastructure_roles_secrets"` SuperUsername string `name:"super_username" default:"postgres"` ReplicationUsername string `name:"replication_username" default:"standby"` - CronAdminUsername string `name:"cron_admin_username" default:""` + AddtionalOwnerRoles []string `name:"additional_owner_roles" default:""` EnablePasswordRotation bool `name:"enable_password_rotation" default:"false"` PasswordRotationInterval uint32 `name:"password_rotation_interval" default:"90"` PasswordRotationUserRetention uint32 `name:"password_rotation_user_retention" default:"180"` diff --git a/pkg/util/users/users.go b/pkg/util/users/users.go index 6bc31f6da..5007268d2 100644 --- a/pkg/util/users/users.go +++ b/pkg/util/users/users.go @@ -53,25 +53,31 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM } } else { r := spec.PgSyncUserRequest{} + r.User = dbUser newMD5Password := util.NewEncryptor(strategy.PasswordEncryption).PGUserPassword(newUser) - if dbUser.Password != newMD5Password { - r.User.Password = newMD5Password - r.Kind = spec.PGsyncUserAlter + // do not compare for roles coming from docker image + if newUser.Origin != spec.RoleOriginSpilo { + if dbUser.Password != newMD5Password { + r.User.Password = newMD5Password + r.Kind = spec.PGsyncUserAlter + } + if addNewFlags, equal := util.SubstractStringSlices(newUser.Flags, dbUser.Flags); !equal { + r.User.Flags = addNewFlags + r.Kind = spec.PGsyncUserAlter + } } if addNewRoles, equal := util.SubstractStringSlices(newUser.MemberOf, dbUser.MemberOf); !equal { r.User.MemberOf = addNewRoles r.Kind = spec.PGsyncUserAlter } - if addNewFlags, equal := util.SubstractStringSlices(newUser.Flags, dbUser.Flags); !equal { - r.User.Flags = addNewFlags - r.Kind = spec.PGsyncUserAlter - } if r.Kind == spec.PGsyncUserAlter { r.User.Name = newUser.Name reqs = append(reqs, r) } - if len(newUser.Parameters) > 0 && !reflect.DeepEqual(dbUser.Parameters, newUser.Parameters) { + if newUser.Origin != spec.RoleOriginSpilo && + len(newUser.Parameters) > 0 && + !reflect.DeepEqual(dbUser.Parameters, newUser.Parameters) { reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncAlterSet, User: newUser}) } }