mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-18 17:18:30 +02:00
Rename roles that are removed from PostgresTeam CRD (#1457)
* rename db roles that are removed from manifests * extend PostgresTeam e2e test * make suffix configurable and add deprecated field to pgUser struct * deny LOGIN from deprecated roles * update feature documentation
This commit is contained in:
@@ -443,6 +443,9 @@ spec:
|
|||||||
enable_postgres_team_crd_superusers:
|
enable_postgres_team_crd_superusers:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
enable_team_member_deprecation:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
enable_team_superuser:
|
enable_team_superuser:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
@@ -465,6 +468,9 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
default:
|
default:
|
||||||
- admin
|
- admin
|
||||||
|
role_deletion_suffix:
|
||||||
|
type: string
|
||||||
|
default: "_deleted"
|
||||||
team_admin_role:
|
team_admin_role:
|
||||||
type: string
|
type: string
|
||||||
default: "admin"
|
default: "admin"
|
||||||
|
|||||||
@@ -289,13 +289,13 @@ configLogicalBackup:
|
|||||||
# automate creation of human users with teams API service
|
# automate creation of human users with teams API service
|
||||||
configTeamsApi:
|
configTeamsApi:
|
||||||
# team_admin_role will have the rights to grant roles coming from PG manifests
|
# team_admin_role will have the rights to grant roles coming from PG manifests
|
||||||
# enable_admin_role_for_users: true
|
enable_admin_role_for_users: true
|
||||||
|
|
||||||
# operator watches for PostgresTeam CRs to assign additional teams and members to clusters
|
# operator watches for PostgresTeam CRs to assign additional teams and members to clusters
|
||||||
enable_postgres_team_crd: false
|
enable_postgres_team_crd: false
|
||||||
# toogle to create additional superuser teams from PostgresTeam CRs
|
# toogle to create additional superuser teams from PostgresTeam CRs
|
||||||
# enable_postgres_team_crd_superusers: false
|
enable_postgres_team_crd_superusers: false
|
||||||
|
# toggle to automatically rename roles of former team members and deny LOGIN
|
||||||
|
enable_team_member_deprecation: false
|
||||||
# toggle to grant superuser to team members created from the Teams API
|
# toggle to grant superuser to team members created from the Teams API
|
||||||
enable_team_superuser: false
|
enable_team_superuser: false
|
||||||
# toggles usage of the Teams API by the operator
|
# toggles usage of the Teams API by the operator
|
||||||
@@ -306,12 +306,13 @@ configTeamsApi:
|
|||||||
# operator will add all team member roles to this group and add a pg_hba line
|
# operator will add all team member roles to this group and add a pg_hba line
|
||||||
pam_role_name: zalandos
|
pam_role_name: zalandos
|
||||||
# List of teams which members need the superuser role in each Postgres cluster
|
# List of teams which members need the superuser role in each Postgres cluster
|
||||||
# postgres_superuser_teams:
|
postgres_superuser_teams:
|
||||||
# - postgres_superusers
|
- postgres_superusers
|
||||||
|
|
||||||
# List of roles that cannot be overwritten by an application, team or infrastructure role
|
# List of roles that cannot be overwritten by an application, team or infrastructure role
|
||||||
protected_role_names:
|
protected_role_names:
|
||||||
- admin
|
- 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
|
# role name to grant to team members created from the Teams API
|
||||||
team_admin_role: admin
|
team_admin_role: admin
|
||||||
# postgres config parameters to apply to each team member role
|
# postgres config parameters to apply to each team member role
|
||||||
|
|||||||
@@ -280,36 +280,32 @@ configLogicalBackup:
|
|||||||
# automate creation of human users with teams API service
|
# automate creation of human users with teams API service
|
||||||
configTeamsApi:
|
configTeamsApi:
|
||||||
# team_admin_role will have the rights to grant roles coming from PG manifests
|
# team_admin_role will have the rights to grant roles coming from PG manifests
|
||||||
# enable_admin_role_for_users: "true"
|
enable_admin_role_for_users: "true"
|
||||||
|
|
||||||
# operator watches for PostgresTeam CRs to assign additional teams and members to clusters
|
# operator watches for PostgresTeam CRs to assign additional teams and members to clusters
|
||||||
enable_postgres_team_crd: "false"
|
enable_postgres_team_crd: "false"
|
||||||
# toogle to create additional superuser teams from PostgresTeam CRs
|
# toogle to create additional superuser teams from PostgresTeam CRs
|
||||||
# enable_postgres_team_crd_superusers: "false"
|
enable_postgres_team_crd_superusers: "false"
|
||||||
|
# toggle to automatically rename roles of former team members and deny LOGIN
|
||||||
|
enable_team_member_deprecation: "false"
|
||||||
# toggle to grant superuser to team members created from the Teams API
|
# toggle to grant superuser to team members created from the Teams API
|
||||||
# enable_team_superuser: "false"
|
enable_team_superuser: "false"
|
||||||
|
|
||||||
# toggles usage of the Teams API by the operator
|
# toggles usage of the Teams API by the operator
|
||||||
enable_teams_api: "false"
|
enable_teams_api: "false"
|
||||||
# should contain a URL to use for authentication (username and token)
|
# should contain a URL to use for authentication (username and token)
|
||||||
# pam_configuration: https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees
|
# pam_configuration: https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees
|
||||||
|
|
||||||
# operator will add all team member roles to this group and add a pg_hba line
|
# operator will add all team member roles to this group and add a pg_hba line
|
||||||
# pam_role_name: zalandos
|
pam_role_name: "zalandos"
|
||||||
|
|
||||||
# List of teams which members need the superuser role in each Postgres cluster
|
# List of teams which members need the superuser role in each Postgres cluster
|
||||||
# postgres_superuser_teams: "postgres_superusers"
|
postgres_superuser_teams: "postgres_superusers"
|
||||||
|
|
||||||
# List of roles that cannot be overwritten by an application, team or infrastructure role
|
# List of roles that cannot be overwritten by an application, team or infrastructure role
|
||||||
# protected_role_names: "admin"
|
protected_role_names: "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
|
# role name to grant to team members created from the Teams API
|
||||||
# team_admin_role: "admin"
|
team_admin_role: "admin"
|
||||||
|
|
||||||
# postgres config parameters to apply to each team member role
|
# postgres config parameters to apply to each team member role
|
||||||
# team_api_role_configuration: "log_statement:all"
|
team_api_role_configuration: "log_statement:all"
|
||||||
|
|
||||||
# URL of the Teams API service
|
# URL of the Teams API service
|
||||||
# teams_api_url: http://fake-teams-api.default.svc.cluster.local
|
# teams_api_url: http://fake-teams-api.default.svc.cluster.local
|
||||||
|
|
||||||
|
|||||||
@@ -704,6 +704,19 @@ key.
|
|||||||
cluster to administer Postgres and maintain infrastructure built around it.
|
cluster to administer Postgres and maintain infrastructure built around it.
|
||||||
The default is empty.
|
The default is empty.
|
||||||
|
|
||||||
|
* **role_deletion_suffix**
|
||||||
|
defines a suffix that - when `enable_team_member_deprecation` is set to
|
||||||
|
`true` - will be appended to database role names of team members that were
|
||||||
|
removed from either the team in the Teams API or a `PostgresTeam` custom
|
||||||
|
resource (additionalMembers). When re-added, the operator will rename roles
|
||||||
|
with the defined suffix back to the original role name.
|
||||||
|
The default is `_deleted`.
|
||||||
|
|
||||||
|
* **enable_team_member_deprecation**
|
||||||
|
if `true` database roles of former team members will be renamed by appending
|
||||||
|
the configured `role_deletion_suffix` and `LOGIN` privilege will be revoked.
|
||||||
|
The default is `false`.
|
||||||
|
|
||||||
* **enable_postgres_team_crd**
|
* **enable_postgres_team_crd**
|
||||||
toggle to make the operator watch for created or updated `PostgresTeam` CRDs
|
toggle to make the operator watch for created or updated `PostgresTeam` CRDs
|
||||||
and create roles for specified additional teams and members.
|
and create roles for specified additional teams and members.
|
||||||
|
|||||||
@@ -407,6 +407,23 @@ spec:
|
|||||||
- "briggs"
|
- "briggs"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Removed members
|
||||||
|
|
||||||
|
The Postgres Operator does not delete database roles when users are removed
|
||||||
|
from manifests. But, using the `PostgresTeam` custom resource or Teams API it
|
||||||
|
is very easy to add roles to many clusters. Manually reverting such a change
|
||||||
|
is cumbersome. Therefore, if members are removed from a `PostgresTeam` or the
|
||||||
|
Teams API the operator can rename roles appending a configured suffix to the
|
||||||
|
name (see `role_deletion_suffix` option) and revoke the `LOGIN` privilege.
|
||||||
|
The suffix makes it easy then for a cleanup script to remove those deprecated
|
||||||
|
roles completely. Switch `enable_team_member_deprecation` to `true` to enable
|
||||||
|
this behavior.
|
||||||
|
|
||||||
|
When a role is re-added to a `PostgresTeam` manifest (or to the source behind
|
||||||
|
the Teams API) the operator will check for roles with the configured suffix
|
||||||
|
and if found, rename the role back to the original name and grant `LOGIN`
|
||||||
|
again.
|
||||||
|
|
||||||
## Prepared databases with roles and default privileges
|
## Prepared databases with roles and default privileges
|
||||||
|
|
||||||
The `users` section in the manifests only allows for creating database roles
|
The `users` section in the manifests only allows for creating database roles
|
||||||
|
|||||||
+60
-15
@@ -197,13 +197,15 @@ class EndToEndTestCase(unittest.TestCase):
|
|||||||
enable_postgres_team_crd = {
|
enable_postgres_team_crd = {
|
||||||
"data": {
|
"data": {
|
||||||
"enable_postgres_team_crd": "true",
|
"enable_postgres_team_crd": "true",
|
||||||
"resync_period": "15s",
|
"enable_team_member_deprecation": "true",
|
||||||
|
"role_deletion_suffix": "_delete_me",
|
||||||
|
"resync_period": "15s"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.k8s.update_config(enable_postgres_team_crd)
|
self.k8s.update_config(enable_postgres_team_crd)
|
||||||
self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"},
|
self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"},
|
||||||
"Operator does not get in sync")
|
"Operator does not get in sync")
|
||||||
|
|
||||||
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
'acid.zalan.do', 'v1', 'default',
|
'acid.zalan.do', 'v1', 'default',
|
||||||
'postgresteams', 'custom-team-membership',
|
'postgresteams', 'custom-team-membership',
|
||||||
@@ -222,18 +224,60 @@ class EndToEndTestCase(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# make sure we let one sync pass and the new user being added
|
|
||||||
time.sleep(15)
|
|
||||||
|
|
||||||
leader = self.k8s.get_cluster_leader_pod()
|
leader = self.k8s.get_cluster_leader_pod()
|
||||||
user_query = """
|
user_query = """
|
||||||
SELECT usename
|
SELECT rolname
|
||||||
FROM pg_catalog.pg_user
|
FROM pg_catalog.pg_roles
|
||||||
WHERE usename IN ('elephant', 'kind');
|
WHERE rolname IN ('elephant', 'kind');
|
||||||
"""
|
"""
|
||||||
users = self.query_database(leader.metadata.name, "postgres", user_query)
|
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
|
||||||
self.eventuallyEqual(lambda: len(users), 2,
|
"Not all additional users found in database", 10, 5)
|
||||||
"Not all additional users found in database: {}".format(users))
|
|
||||||
|
# replace additional member and check if the removed member's role is renamed
|
||||||
|
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
|
'acid.zalan.do', 'v1', 'default',
|
||||||
|
'postgresteams', 'custom-team-membership',
|
||||||
|
{
|
||||||
|
'spec': {
|
||||||
|
'additionalMembers': {
|
||||||
|
'e2e': [
|
||||||
|
'tester'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
user_query = """
|
||||||
|
SELECT rolname
|
||||||
|
FROM pg_catalog.pg_roles
|
||||||
|
WHERE (rolname = 'tester' AND rolcanlogin)
|
||||||
|
OR (rolname = 'kind_delete_me' AND NOT rolcanlogin);
|
||||||
|
"""
|
||||||
|
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
|
||||||
|
"Database role of replaced member in PostgresTeam not renamed", 10, 5)
|
||||||
|
|
||||||
|
# re-add additional member and check if the role is renamed back
|
||||||
|
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
|
'acid.zalan.do', 'v1', 'default',
|
||||||
|
'postgresteams', 'custom-team-membership',
|
||||||
|
{
|
||||||
|
'spec': {
|
||||||
|
'additionalMembers': {
|
||||||
|
'e2e': [
|
||||||
|
'kind'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
user_query = """
|
||||||
|
SELECT rolname
|
||||||
|
FROM pg_catalog.pg_roles
|
||||||
|
WHERE (rolname = 'kind' AND rolcanlogin)
|
||||||
|
OR (rolname = 'tester_delete_me' AND NOT rolcanlogin);
|
||||||
|
"""
|
||||||
|
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
|
||||||
|
"Database role of recreated member in PostgresTeam not renamed back to original name", 10, 5)
|
||||||
|
|
||||||
# revert config change
|
# revert config change
|
||||||
revert_resync = {
|
revert_resync = {
|
||||||
@@ -407,9 +451,9 @@ class EndToEndTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
leader = k8s.get_cluster_leader_pod()
|
leader = k8s.get_cluster_leader_pod()
|
||||||
schemas_query = """
|
schemas_query = """
|
||||||
select schema_name
|
SELECT schema_name
|
||||||
from information_schema.schemata
|
FROM information_schema.schemata
|
||||||
where schema_name = 'pooler'
|
WHERE schema_name = 'pooler'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
db_list = self.list_databases(leader.metadata.name)
|
db_list = self.list_databases(leader.metadata.name)
|
||||||
@@ -529,6 +573,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||||||
"Parameters": None,
|
"Parameters": None,
|
||||||
"AdminRole": "",
|
"AdminRole": "",
|
||||||
"Origin": 2,
|
"Origin": 2,
|
||||||
|
"Deleted": False
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
@@ -1417,7 +1462,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||||||
k8s = self.k8s
|
k8s = self.k8s
|
||||||
result_set = []
|
result_set = []
|
||||||
db_list = []
|
db_list = []
|
||||||
db_list_query = "select datname from pg_database"
|
db_list_query = "SELECT datname FROM pg_database"
|
||||||
exec_query = r"psql -tAq -c \"{}\" -d {}"
|
exec_query = r"psql -tAq -c \"{}\" -d {}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ data:
|
|||||||
# enable_shm_volume: "true"
|
# enable_shm_volume: "true"
|
||||||
# enable_sidecars: "true"
|
# enable_sidecars: "true"
|
||||||
enable_spilo_wal_path_compat: "true"
|
enable_spilo_wal_path_compat: "true"
|
||||||
|
enable_team_member_deprecation: "false"
|
||||||
# enable_team_superuser: "false"
|
# enable_team_superuser: "false"
|
||||||
enable_teams_api: "false"
|
enable_teams_api: "false"
|
||||||
# etcd_host: ""
|
# etcd_host: ""
|
||||||
@@ -111,6 +112,7 @@ data:
|
|||||||
resource_check_timeout: 10m
|
resource_check_timeout: 10m
|
||||||
resync_period: 30m
|
resync_period: 30m
|
||||||
ring_log_lines: "100"
|
ring_log_lines: "100"
|
||||||
|
role_deletion_suffix: "_deleted"
|
||||||
secret_name_template: "{username}.{cluster}.credentials"
|
secret_name_template: "{username}.{cluster}.credentials"
|
||||||
# sidecar_docker_images: ""
|
# sidecar_docker_images: ""
|
||||||
# set_memory_request_to_limit: "false"
|
# set_memory_request_to_limit: "false"
|
||||||
|
|||||||
@@ -439,6 +439,9 @@ spec:
|
|||||||
enable_postgres_team_crd_superusers:
|
enable_postgres_team_crd_superusers:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
enable_team_member_deprecation:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
enable_team_superuser:
|
enable_team_superuser:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
@@ -461,6 +464,9 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
default:
|
default:
|
||||||
- admin
|
- admin
|
||||||
|
role_deletion_suffix:
|
||||||
|
type: string
|
||||||
|
default: "_deleted"
|
||||||
team_admin_role:
|
team_admin_role:
|
||||||
type: string
|
type: string
|
||||||
default: "admin"
|
default: "admin"
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ configuration:
|
|||||||
# enable_admin_role_for_users: true
|
# enable_admin_role_for_users: true
|
||||||
# enable_postgres_team_crd: false
|
# enable_postgres_team_crd: false
|
||||||
# enable_postgres_team_crd_superusers: false
|
# enable_postgres_team_crd_superusers: false
|
||||||
|
enable_team_member_deprecation: false
|
||||||
enable_team_superuser: false
|
enable_team_superuser: false
|
||||||
enable_teams_api: false
|
enable_teams_api: false
|
||||||
# pam_configuration: ""
|
# pam_configuration: ""
|
||||||
@@ -149,6 +150,7 @@ configuration:
|
|||||||
# - postgres_superusers
|
# - postgres_superusers
|
||||||
protected_role_names:
|
protected_role_names:
|
||||||
- admin
|
- admin
|
||||||
|
role_deletion_suffix: "_deleted"
|
||||||
team_admin_role: admin
|
team_admin_role: admin
|
||||||
team_api_role_configuration:
|
team_api_role_configuration:
|
||||||
log_statement: all
|
log_statement: all
|
||||||
|
|||||||
@@ -1377,6 +1377,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
|
|||||||
"enable_postgres_team_crd_superusers": {
|
"enable_postgres_team_crd_superusers": {
|
||||||
Type: "boolean",
|
Type: "boolean",
|
||||||
},
|
},
|
||||||
|
"enable_team_member_deprecation": {
|
||||||
|
Type: "boolean",
|
||||||
|
},
|
||||||
"enable_team_superuser": {
|
"enable_team_superuser": {
|
||||||
Type: "boolean",
|
Type: "boolean",
|
||||||
},
|
},
|
||||||
@@ -1405,6 +1408,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"role_deletion_suffix": {
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
"team_admin_role": {
|
"team_admin_role": {
|
||||||
Type: "string",
|
Type: "string",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ type TeamsAPIConfiguration struct {
|
|||||||
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
|
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
|
||||||
EnablePostgresTeamCRD bool `json:"enable_postgres_team_crd,omitempty"`
|
EnablePostgresTeamCRD bool `json:"enable_postgres_team_crd,omitempty"`
|
||||||
EnablePostgresTeamCRDSuperusers bool `json:"enable_postgres_team_crd_superusers,omitempty"`
|
EnablePostgresTeamCRDSuperusers bool `json:"enable_postgres_team_crd_superusers,omitempty"`
|
||||||
|
EnableTeamMemberDeprecation bool `json:"enable_team_member_deprecation,omitempty"`
|
||||||
|
RoleDeletionSuffix string `json:"role_deletion_suffix,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoggingRESTAPIConfiguration defines Logging API conf
|
// LoggingRESTAPIConfiguration defines Logging API conf
|
||||||
|
|||||||
+16
-2
@@ -74,6 +74,7 @@ type Cluster struct {
|
|||||||
eventRecorder record.EventRecorder
|
eventRecorder record.EventRecorder
|
||||||
patroni patroni.Interface
|
patroni patroni.Interface
|
||||||
pgUsers map[string]spec.PgUser
|
pgUsers map[string]spec.PgUser
|
||||||
|
pgUsersCache map[string]spec.PgUser
|
||||||
systemUsers map[string]spec.PgUser
|
systemUsers map[string]spec.PgUser
|
||||||
podSubscribers map[spec.NamespacedName]chan PodEvent
|
podSubscribers map[spec.NamespacedName]chan PodEvent
|
||||||
podSubscribersMu sync.RWMutex
|
podSubscribersMu sync.RWMutex
|
||||||
@@ -129,7 +130,9 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
|
|||||||
Secrets: make(map[types.UID]*v1.Secret),
|
Secrets: make(map[types.UID]*v1.Secret),
|
||||||
Services: make(map[PostgresRole]*v1.Service),
|
Services: make(map[PostgresRole]*v1.Service),
|
||||||
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
|
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
|
||||||
userSyncStrategy: users.DefaultUserSyncStrategy{PasswordEncryption: passwordEncryption},
|
userSyncStrategy: users.DefaultUserSyncStrategy{
|
||||||
|
PasswordEncryption: passwordEncryption,
|
||||||
|
RoleDeletionSuffix: cfg.OpConfig.RoleDeletionSuffix},
|
||||||
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
|
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
|
||||||
podEventsQueue: podEventsQueue,
|
podEventsQueue: podEventsQueue,
|
||||||
KubeClient: kubeClient,
|
KubeClient: kubeClient,
|
||||||
@@ -190,6 +193,17 @@ func (c *Cluster) isNewCluster() bool {
|
|||||||
func (c *Cluster) initUsers() error {
|
func (c *Cluster) initUsers() error {
|
||||||
c.setProcessName("initializing users")
|
c.setProcessName("initializing users")
|
||||||
|
|
||||||
|
// if team member deprecation is enabled save current state of pgUsers
|
||||||
|
// to check for deleted roles
|
||||||
|
c.pgUsersCache = map[string]spec.PgUser{}
|
||||||
|
if c.OpConfig.EnableTeamMemberDeprecation {
|
||||||
|
for k, v := range c.pgUsers {
|
||||||
|
if v.Origin == spec.RoleOriginTeamsAPI {
|
||||||
|
c.pgUsersCache[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// clear our the previous state of the cluster users (in case we are
|
// clear our the previous state of the cluster users (in case we are
|
||||||
// running a sync).
|
// running a sync).
|
||||||
c.systemUsers = map[string]spec.PgUser{}
|
c.systemUsers = map[string]spec.PgUser{}
|
||||||
@@ -650,7 +664,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||||||
needConnectionPooler := needMasterConnectionPoolerWorker(&newSpec.Spec) ||
|
needConnectionPooler := needMasterConnectionPoolerWorker(&newSpec.Spec) ||
|
||||||
needReplicaConnectionPoolerWorker(&newSpec.Spec)
|
needReplicaConnectionPoolerWorker(&newSpec.Spec)
|
||||||
if !sameUsers || needConnectionPooler {
|
if !sameUsers || needConnectionPooler {
|
||||||
c.logger.Debugf("syncing secrets")
|
c.logger.Debugf("initialize users")
|
||||||
if err := c.initUsers(); err != nil {
|
if err := c.initUsers(); err != nil {
|
||||||
c.logger.Errorf("could not init users: %v", err)
|
c.logger.Errorf("could not init users: %v", err)
|
||||||
updateFailed = true
|
updateFailed = true
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
|
|||||||
rolname, rolpassword string
|
rolname, rolpassword string
|
||||||
rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool
|
rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool
|
||||||
roloptions, memberof []string
|
roloptions, memberof []string
|
||||||
|
roldeleted bool
|
||||||
)
|
)
|
||||||
err := rows.Scan(&rolname, &rolpassword, &rolsuper, &rolinherit,
|
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))
|
||||||
@@ -216,7 +217,11 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
|
|||||||
parameters[fields[0]] = fields[1]
|
parameters[fields[0]] = fields[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters}
|
if strings.HasSuffix(rolname, c.OpConfig.RoleDeletionSuffix) {
|
||||||
|
roldeleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof, Parameters: parameters, Deleted: roldeleted}
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
|
|||||||
@@ -551,10 +551,29 @@ func (c *Cluster) syncRoles() (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// mapping between original role name and with deletion suffix
|
||||||
|
deletedUsers := map[string]string{}
|
||||||
|
|
||||||
|
// create list of database roles to query
|
||||||
for _, u := range c.pgUsers {
|
for _, u := range c.pgUsers {
|
||||||
userNames = append(userNames, u.Name)
|
userNames = append(userNames, u.Name)
|
||||||
|
// add team member role name with rename suffix in case we need to rename it back
|
||||||
|
if u.Origin == spec.RoleOriginTeamsAPI && c.OpConfig.EnableTeamMemberDeprecation {
|
||||||
|
deletedUsers[u.Name+c.OpConfig.RoleDeletionSuffix] = u.Name
|
||||||
|
userNames = append(userNames, u.Name+c.OpConfig.RoleDeletionSuffix)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add team members that exist only in cache
|
||||||
|
// to trigger a rename of the role in ProduceSyncRequests
|
||||||
|
for _, cachedUser := range c.pgUsersCache {
|
||||||
|
if _, exists := c.pgUsers[cachedUser.Name]; !exists {
|
||||||
|
userNames = append(userNames, cachedUser.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add pooler user to list of pgUsers, too
|
||||||
|
// to check if the pooler user exists or has to be created
|
||||||
if needMasterConnectionPooler(&c.Spec) || needReplicaConnectionPooler(&c.Spec) {
|
if needMasterConnectionPooler(&c.Spec) || needReplicaConnectionPooler(&c.Spec) {
|
||||||
connectionPoolerUser := c.systemUsers[constants.ConnectionPoolerUserKeyName]
|
connectionPoolerUser := c.systemUsers[constants.ConnectionPoolerUserKeyName]
|
||||||
userNames = append(userNames, connectionPoolerUser.Name)
|
userNames = append(userNames, connectionPoolerUser.Name)
|
||||||
@@ -569,6 +588,16 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update pgUsers where a deleted role was found
|
||||||
|
// so that they are skipped in ProduceSyncRequests
|
||||||
|
for _, dbUser := range dbUsers {
|
||||||
|
if originalUser, exists := deletedUsers[dbUser.Name]; exists {
|
||||||
|
recreatedUser := c.pgUsers[originalUser]
|
||||||
|
recreatedUser.Deleted = true
|
||||||
|
c.pgUsers[originalUser] = recreatedUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, c.pgUsers)
|
pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, c.pgUsers)
|
||||||
if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil {
|
if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil {
|
||||||
return fmt.Errorf("error executing sync statements: %v", err)
|
return fmt.Errorf("error executing sync statements: %v", err)
|
||||||
|
|||||||
+3
-5
@@ -242,7 +242,7 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) {
|
|||||||
for team, membership := range *c.Config.PgTeamMap {
|
for team, membership := range *c.Config.PgTeamMap {
|
||||||
if team == teamID {
|
if team == teamID {
|
||||||
additionalMembers = membership.AdditionalMembers
|
additionalMembers = membership.AdditionalMembers
|
||||||
c.logger.Debugf("found %d additional members for team %q", len(members), teamID)
|
c.logger.Debugf("found %d additional members for team %q", len(additionalMembers), teamID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,14 +256,12 @@ func (c *Cluster) getTeamMembers(teamID string) ([]string, error) {
|
|||||||
|
|
||||||
token, err := c.oauthTokenGetter.getOAuthToken()
|
token, err := c.oauthTokenGetter.getOAuthToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warnf("could not get oauth token to authenticate to team service API, only returning %d members for team %q: %v", len(members), teamID, err)
|
return nil, fmt.Errorf("could not get oauth token to authenticate to team service API: %v", err)
|
||||||
return members, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token)
|
teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warnf("could not get team info for team %q, only returning %d members: %v", teamID, len(members), err)
|
return nil, fmt.Errorf("could not get team info for team %q: %v", teamID, err)
|
||||||
return members, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, member := range teamInfo.Members {
|
for _, member := range teamInfo.Members {
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
|||||||
result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams
|
result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams
|
||||||
result.EnablePostgresTeamCRD = fromCRD.TeamsAPI.EnablePostgresTeamCRD
|
result.EnablePostgresTeamCRD = fromCRD.TeamsAPI.EnablePostgresTeamCRD
|
||||||
result.EnablePostgresTeamCRDSuperusers = fromCRD.TeamsAPI.EnablePostgresTeamCRDSuperusers
|
result.EnablePostgresTeamCRDSuperusers = fromCRD.TeamsAPI.EnablePostgresTeamCRDSuperusers
|
||||||
|
result.EnableTeamMemberDeprecation = fromCRD.TeamsAPI.EnableTeamMemberDeprecation
|
||||||
|
result.RoleDeletionSuffix = util.Coalesce(fromCRD.TeamsAPI.RoleDeletionSuffix, "_deleted")
|
||||||
|
|
||||||
// logging REST API config
|
// logging REST API config
|
||||||
result.APIPort = util.CoalesceInt(fromCRD.LoggingRESTAPI.APIPort, 8080)
|
result.APIPort = util.CoalesceInt(fromCRD.LoggingRESTAPI.APIPort, 8080)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const (
|
|||||||
PGSyncUserAdd = iota
|
PGSyncUserAdd = iota
|
||||||
PGsyncUserAlter
|
PGsyncUserAlter
|
||||||
PGSyncAlterSet // handle ALTER ROLE SET parameter = value
|
PGSyncAlterSet // handle ALTER ROLE SET parameter = value
|
||||||
|
PGSyncUserRename
|
||||||
)
|
)
|
||||||
|
|
||||||
// PgUser contains information about a single user.
|
// PgUser contains information about a single user.
|
||||||
@@ -53,6 +54,7 @@ type PgUser struct {
|
|||||||
MemberOf []string `yaml:"inrole"`
|
MemberOf []string `yaml:"inrole"`
|
||||||
Parameters map[string]string `yaml:"db_parameters"`
|
Parameters map[string]string `yaml:"db_parameters"`
|
||||||
AdminRole string `yaml:"admin_role"`
|
AdminRole string `yaml:"admin_role"`
|
||||||
|
Deleted bool `yaml:"deleted"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *PgUser) Valid() bool {
|
func (user *PgUser) Valid() bool {
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
True = true
|
|
||||||
False = false
|
|
||||||
pgTeamList = acidv1.PostgresTeamList{
|
pgTeamList = acidv1.PostgresTeamList{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "List",
|
Kind: "List",
|
||||||
|
|||||||
@@ -176,6 +176,8 @@ type Config struct {
|
|||||||
EnableTeamsAPI bool `name:"enable_teams_api" default:"true"`
|
EnableTeamsAPI bool `name:"enable_teams_api" default:"true"`
|
||||||
EnableTeamSuperuser bool `name:"enable_team_superuser" default:"false"`
|
EnableTeamSuperuser bool `name:"enable_team_superuser" default:"false"`
|
||||||
TeamAdminRole string `name:"team_admin_role" default:"admin"`
|
TeamAdminRole string `name:"team_admin_role" default:"admin"`
|
||||||
|
RoleDeletionSuffix string `name:"role_deletion_suffix" default:"_deleted"`
|
||||||
|
EnableTeamMemberDeprecation bool `name:"enable_team_member_deprecation" default:"false"`
|
||||||
EnableAdminRoleForUsers bool `name:"enable_admin_role_for_users" default:"true"`
|
EnableAdminRoleForUsers bool `name:"enable_admin_role_for_users" default:"true"`
|
||||||
EnablePostgresTeamCRD bool `name:"enable_postgres_team_crd" default:"false"`
|
EnablePostgresTeamCRD bool `name:"enable_postgres_team_crd" default:"false"`
|
||||||
EnablePostgresTeamCRDSuperusers bool `name:"enable_postgres_team_crd_superusers" default:"false"`
|
EnablePostgresTeamCRDSuperusers bool `name:"enable_postgres_team_crd_superusers" default:"false"`
|
||||||
|
|||||||
+48
-1
@@ -9,11 +9,13 @@ 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/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
createUserSQL = `SET LOCAL synchronous_commit = 'local'; CREATE ROLE "%s" %s %s;`
|
createUserSQL = `SET LOCAL synchronous_commit = 'local'; CREATE ROLE "%s" %s %s;`
|
||||||
alterUserSQL = `ALTER ROLE "%s" %s`
|
alterUserSQL = `ALTER ROLE "%s" %s`
|
||||||
|
alterUserRenameSQL = `ALTER ROLE "%s" RENAME TO "%s%s"`
|
||||||
alterRoleResetAllSQL = `ALTER ROLE "%s" RESET ALL`
|
alterRoleResetAllSQL = `ALTER ROLE "%s" RESET ALL`
|
||||||
alterRoleSetSQL = `ALTER ROLE "%s" SET %s TO %s`
|
alterRoleSetSQL = `ALTER ROLE "%s" SET %s TO %s`
|
||||||
grantToUserSQL = `GRANT %s TO "%s"`
|
grantToUserSQL = `GRANT %s TO "%s"`
|
||||||
@@ -29,6 +31,7 @@ const (
|
|||||||
// (except for the NOLOGIN). TODO: process other NOflags, i.e. NOSUPERUSER correctly.
|
// (except for the NOLOGIN). TODO: process other NOflags, i.e. NOSUPERUSER correctly.
|
||||||
type DefaultUserSyncStrategy struct {
|
type DefaultUserSyncStrategy struct {
|
||||||
PasswordEncryption string
|
PasswordEncryption string
|
||||||
|
RoleDeletionSuffix string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProduceSyncRequests figures out the types of changes that need to happen with the given users.
|
// ProduceSyncRequests figures out the types of changes that need to happen with the given users.
|
||||||
@@ -36,8 +39,11 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
|
|||||||
newUsers spec.PgUserMap) []spec.PgSyncUserRequest {
|
newUsers spec.PgUserMap) []spec.PgSyncUserRequest {
|
||||||
|
|
||||||
var reqs []spec.PgSyncUserRequest
|
var reqs []spec.PgSyncUserRequest
|
||||||
// No existing roles are deleted or stripped of role memebership/flags
|
|
||||||
for name, newUser := range newUsers {
|
for name, newUser := range newUsers {
|
||||||
|
// do not create user that exists in DB with deletion suffix
|
||||||
|
if newUser.Deleted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
dbUser, exists := dbUsers[name]
|
dbUser, exists := dbUsers[name]
|
||||||
if !exists {
|
if !exists {
|
||||||
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserAdd, User: newUser})
|
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserAdd, User: newUser})
|
||||||
@@ -70,6 +76,25 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No existing roles are deleted or stripped of role membership/flags
|
||||||
|
// but team roles will be renamed and denied from LOGIN
|
||||||
|
for name, dbUser := range dbUsers {
|
||||||
|
if _, exists := newUsers[name]; !exists {
|
||||||
|
// toggle LOGIN flag based on role deletion
|
||||||
|
userFlags := make([]string, len(dbUser.Flags))
|
||||||
|
userFlags = append(userFlags, dbUser.Flags...)
|
||||||
|
if dbUser.Deleted {
|
||||||
|
dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagNoLogin, constants.RoleFlagLogin)
|
||||||
|
} else {
|
||||||
|
dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagLogin, constants.RoleFlagNoLogin)
|
||||||
|
}
|
||||||
|
if !util.IsEqualIgnoreOrder(userFlags, dbUser.Flags) {
|
||||||
|
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGsyncUserAlter, User: dbUser})
|
||||||
|
}
|
||||||
|
|
||||||
|
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserRename, User: dbUser})
|
||||||
|
}
|
||||||
|
}
|
||||||
return reqs
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +119,11 @@ func (strategy DefaultUserSyncStrategy) ExecuteSyncRequests(requests []spec.PgSy
|
|||||||
reqretries = append(reqretries, request)
|
reqretries = append(reqretries, request)
|
||||||
errors = append(errors, fmt.Sprintf("could not set custom user %q parameters: %v", request.User.Name, err))
|
errors = append(errors, fmt.Sprintf("could not set custom user %q parameters: %v", request.User.Name, err))
|
||||||
}
|
}
|
||||||
|
case spec.PGSyncUserRename:
|
||||||
|
if err := strategy.alterPgUserRename(request.User, db); err != nil {
|
||||||
|
reqretries = append(reqretries, request)
|
||||||
|
errors = append(errors, fmt.Sprintf("could not rename custom user %q: %v", request.User.Name, err))
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unrecognized operation: %v", request.Kind)
|
return fmt.Errorf("unrecognized operation: %v", request.Kind)
|
||||||
}
|
}
|
||||||
@@ -124,6 +154,23 @@ func (strategy DefaultUserSyncStrategy) alterPgUserSet(user spec.PgUser, db *sql
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (strategy DefaultUserSyncStrategy) alterPgUserRename(user spec.PgUser, db *sql.DB) error {
|
||||||
|
var query string
|
||||||
|
|
||||||
|
// append or trim deletion suffix depending if the user has the suffix or not
|
||||||
|
if user.Deleted {
|
||||||
|
newName := strings.TrimSuffix(user.Name, strategy.RoleDeletionSuffix)
|
||||||
|
query = fmt.Sprintf(alterUserRenameSQL, user.Name, newName, "")
|
||||||
|
} else {
|
||||||
|
query = fmt.Sprintf(alterUserRenameSQL, user.Name, user.Name, strategy.RoleDeletionSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Exec(query); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (strategy DefaultUserSyncStrategy) createPgUser(user spec.PgUser, db *sql.DB) error {
|
func (strategy DefaultUserSyncStrategy) createPgUser(user spec.PgUser, db *sql.DB) error {
|
||||||
var userFlags []string
|
var userFlags []string
|
||||||
var userPassword string
|
var userPassword string
|
||||||
|
|||||||
@@ -151,6 +151,18 @@ func IsEqualIgnoreOrder(a, b []string) bool {
|
|||||||
return reflect.DeepEqual(a_copy, b_copy)
|
return reflect.DeepEqual(a_copy, b_copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SliceReplaceElement
|
||||||
|
func StringSliceReplaceElement(s []string, a, b string) (result []string) {
|
||||||
|
tmp := make([]string, 0, len(s))
|
||||||
|
for _, str := range s {
|
||||||
|
if str == a {
|
||||||
|
str = b
|
||||||
|
}
|
||||||
|
tmp = append(tmp, str)
|
||||||
|
}
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
// SubstractStringSlices finds elements in a that are not in b and return them as a result slice.
|
// SubstractStringSlices finds elements in a that are not in b and return them as a result slice.
|
||||||
func SubstractStringSlices(a []string, b []string) (result []string, equal bool) {
|
func SubstractStringSlices(a []string, b []string) (result []string, equal bool) {
|
||||||
// Slices are assumed to contain unique elements only
|
// Slices are assumed to contain unique elements only
|
||||||
|
|||||||
@@ -166,6 +166,14 @@ func TestIsEqualIgnoreOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringSliceReplaceElement(t *testing.T) {
|
||||||
|
testSlice := []string{"a", "b", "c"}
|
||||||
|
testSlice = StringSliceReplaceElement(testSlice, "b", "d")
|
||||||
|
if !SliceContains(testSlice, "d") {
|
||||||
|
t.Errorf("testSlide item not replaced: %v", testSlice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubstractSlices(t *testing.T) {
|
func TestSubstractSlices(t *testing.T) {
|
||||||
for _, tt := range substractTest {
|
for _, tt := range substractTest {
|
||||||
actualRes, actualEqual := SubstractStringSlices(tt.inA, tt.inB)
|
actualRes, actualEqual := SubstractStringSlices(tt.inA, tt.inB)
|
||||||
|
|||||||
Reference in New Issue
Block a user