From 89801ef30a68643fcf44fb0cdde0296f83f2b33d Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Wed, 2 Mar 2022 18:45:48 +0100 Subject: [PATCH] grant db owners to cron_admin --- .../crds/operatorconfigurations.yaml | 2 ++ charts/postgres-operator/values.yaml | 8 +++++ docs/reference/operator_parameters.md | 7 ++++ manifests/configmap.yaml | 3 +- manifests/operatorconfiguration.crd.yaml | 2 ++ ...gresql-operator-default-configuration.yaml | 2 ++ pkg/apis/acid.zalan.do/v1/crds.go | 12 +++++++ .../v1/operator_configuration_type.go | 1 + pkg/cluster/cluster.go | 36 +++++++++++++++++++ pkg/cluster/k8sres.go | 2 +- pkg/controller/operator_config.go | 3 +- pkg/spec/types.go | 1 + pkg/util/config/config.go | 3 +- 13 files changed, 78 insertions(+), 4 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index f510e08f5..b801ed331 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -130,6 +130,8 @@ spec: users: type: object properties: + cron_admin_username: + type: string enable_password_rotation: type: boolean default: false diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 288efe763..f78a358c6 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -59,6 +59,14 @@ 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 + # 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 + password_rotation_interval: 90 + # retention interval to keep rotation users + password_rotation_user_retention: 180 # postgres username used for replication between instances replication_username: standby # postgres superuser name to be created by initdb diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index f3d9be88f..0059bd103 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -177,6 +177,13 @@ 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`. + * **enable_password_rotation** For all `LOGIN` roles that are not database owners the operator can rotate credentials in the corresponding K8s secrets by replacing the username and diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index b3aaa3c66..a3c033745 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -23,6 +23,7 @@ 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 @@ -109,7 +110,7 @@ data: # pod_service_account_role_binding_definition: "" pod_terminate_grace_period: 5m # postgres_superuser_teams: "postgres_superusers" - # protected_role_names: "admin" + # protected_role_names: "admin,cron_admin" ready_wait_interval: 3s ready_wait_timeout: 30s repair_period: 5m diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index d086998cf..ff99a6e7b 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -128,6 +128,8 @@ spec: users: type: object properties: + cron_admin_username: + type: string enable_password_rotation: type: boolean default: false diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 87b5436d5..28aaabb21 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -31,6 +31,7 @@ configuration: 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: @@ -163,6 +164,7 @@ configuration: # - postgres_superusers protected_role_names: - admin + - cron_admin role_deletion_suffix: "_deleted" team_admin_role: admin team_api_role_configuration: diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 9dc3d167e..043c8aaea 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -1142,6 +1142,18 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "users": { Type: "object", Properties: map[string]apiextv1.JSONSchemaProps{ + "cron_admin_username": { + Type: "string", + }, + "enable_password_rotation": { + Type: "boolean", + }, + "password_rotation_interval": { + Type: "integer", + }, + "password_rotation_user_retention": { + Type: "integer", + }, "replication_username": { Type: "string", }, 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 1298c6834..7fbd5f9de 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -39,6 +39,7 @@ type OperatorConfigurationList struct { 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"` diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 2afacde99..8ac0ab1ff 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -228,6 +228,8 @@ func (c *Cluster) initUsers() error { return fmt.Errorf("could not init human users: %v", err) } + c.initCronAdmin() + return nil } @@ -1297,6 +1299,40 @@ 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) + } + } + + 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 + } + } +} + func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) error { teamMembers, err := c.getTeamMembers(teamID) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index e741c6dc4..3f558820e 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1622,7 +1622,7 @@ func (c *Cluster) generateUserSecrets() map[string]*v1.Secret { func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser) *v1.Secret { //Skip users with no password i.e. human users (they'll be authenticated using pam) if pgUser.Password == "" { - if pgUser.Origin != spec.RoleOriginTeamsAPI { + if pgUser.Origin != spec.RoleOriginTeamsAPI && pgUser.Origin != spec.RoleOriginSpilo { c.logger.Warningf("could not generate secret for a non-teamsAPI role %q: role has no password", pgUser.Name) } diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index fbf12bfb9..ccf2bc424 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -55,6 +55,7 @@ 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.CronAdminUsername = fromCRD.PostgresUsersConfiguration.CronAdminUsername result.EnablePasswordRotation = fromCRD.PostgresUsersConfiguration.EnablePasswordRotation result.PasswordRotationInterval = util.CoalesceUInt32(fromCRD.PostgresUsersConfiguration.PasswordRotationInterval, 90) result.PasswordRotationUserRetention = util.CoalesceUInt32(fromCRD.PostgresUsersConfiguration.DeepCopy().PasswordRotationUserRetention, 180) @@ -186,7 +187,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.TeamAdminRole = fromCRD.TeamsAPI.TeamAdminRole result.PamRoleName = util.Coalesce(fromCRD.TeamsAPI.PamRoleName, "zalandos") result.PamConfiguration = util.Coalesce(fromCRD.TeamsAPI.PamConfiguration, "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees") - result.ProtectedRoles = util.CoalesceStrArr(fromCRD.TeamsAPI.ProtectedRoles, []string{"admin"}) + result.ProtectedRoles = util.CoalesceStrArr(fromCRD.TeamsAPI.ProtectedRoles, []string{"admin", "cron_admin"}) result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams result.EnablePostgresTeamCRD = fromCRD.TeamsAPI.EnablePostgresTeamCRD result.EnablePostgresTeamCRDSuperusers = fromCRD.TeamsAPI.EnablePostgresTeamCRDSuperusers diff --git a/pkg/spec/types.go b/pkg/spec/types.go index 202e0fa6f..428bcbdc6 100644 --- a/pkg/spec/types.go +++ b/pkg/spec/types.go @@ -30,6 +30,7 @@ const ( RoleOriginManifest RoleOriginInfrastructure RoleOriginTeamsAPI + RoleOriginSpilo RoleOriginSystem RoleOriginBootstrap RoleConnectionPooler diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 0dc1004a7..139a0810e 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -101,6 +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:""` 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"` @@ -210,7 +211,7 @@ type Config struct { TeamAPIRoleConfiguration map[string]string `name:"team_api_role_configuration" default:"log_statement:all"` PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` PodManagementPolicy string `name:"pod_management_policy" default:"ordered_ready"` - ProtectedRoles []string `name:"protected_role_names" default:"admin"` + ProtectedRoles []string `name:"protected_role_names" default:"admin,cron_admin"` PostgresSuperuserTeams []string `name:"postgres_superuser_teams" default:""` SetMemoryRequestToLimit bool `name:"set_memory_request_to_limit" default:"false"` EnableLazySpiloUpgrade bool `name:"enable_lazy_spilo_upgrade" default:"false"`