reverse membership for additional owner roles (#1862)
* reverse membership for additional owner roles * remove type RoleOriginSpilo * use e2e images with cron_admin inside * let operator resolve reversed membership * make additional owner roles part of the sync user strategy * add more context in the docs about additional_owner_roles
This commit is contained in:
		
							parent
							
								
									9eb7517218
								
							
						
					
					
						commit
						a77d5df158
					
				|  | @ -178,13 +178,18 @@ under the `users` key. | ||||||
|   `standby`. |   `standby`. | ||||||
| 
 | 
 | ||||||
| * **additional_owner_roles** | * **additional_owner_roles** | ||||||
|   Specifies database roles that will become members of all database owners. |   Specifies database roles that will be granted to all database owners. Owners | ||||||
|   Then owners can use `SET ROLE` to obtain privileges of these roles to e.g. |   can then use `SET ROLE` to obtain privileges of these roles to e.g. create | ||||||
|   create/update functionality from extensions as part of a migration script. |   or update functionality from extensions as part of a migration script. One | ||||||
|   Note, that roles listed here should be preconfigured in the docker image |   such role can be `cron_admin` which is provided by the Spilo docker image to | ||||||
|   and already exist in the database cluster on startup. One such role can be |   set up cron jobs inside the `postgres` database. In general, roles listed | ||||||
|   `cron_admin` which is provided by the Spilo docker image to set up cron |   here should be preconfigured in the docker image and already exist in the | ||||||
|   jobs inside the `postgres` database. Default is `empty`. |   database cluster on startup. Otherwise, syncing roles will return an error | ||||||
|  |   on each cluster sync process. Alternatively, you have to create the role and | ||||||
|  |   do the GRANT manually. Note, the operator will not allow additional owner | ||||||
|  |   roles to be members of database owners because it should be vice versa. If | ||||||
|  |   the operator cannot set up the correct membership it tries to revoke all | ||||||
|  |   additional owner roles from database owners. Default is `empty`. | ||||||
| 
 | 
 | ||||||
| * **enable_password_rotation** | * **enable_password_rotation** | ||||||
|   For all `LOGIN` roles that are not database owners the operator can rotate |   For all `LOGIN` roles that are not database owners the operator can rotate | ||||||
|  |  | ||||||
|  | @ -12,8 +12,8 @@ from kubernetes import client | ||||||
| from tests.k8s_api import K8s | from tests.k8s_api import K8s | ||||||
| from kubernetes.client.rest import ApiException | from kubernetes.client.rest import ApiException | ||||||
| 
 | 
 | ||||||
| SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-14-e2e:0.1" | SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-14-e2e:0.3" | ||||||
| SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-14-e2e:0.2" | SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-14-e2e:0.4" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def to_selector(labels): | def to_selector(labels): | ||||||
|  | @ -161,10 +161,21 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|     @timeout_decorator.timeout(TEST_TIMEOUT_SEC) |     @timeout_decorator.timeout(TEST_TIMEOUT_SEC) | ||||||
|     def test_additional_owner_roles(self): |     def test_additional_owner_roles(self): | ||||||
|         ''' |         ''' | ||||||
|            Test adding additional member roles to existing database owner roles |            Test granting additional roles to existing database owners | ||||||
|         ''' |         ''' | ||||||
|         k8s = self.k8s |         k8s = self.k8s | ||||||
| 
 | 
 | ||||||
|  |         # first test - wait for the operator to get in sync and set everything up | ||||||
|  |         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, | ||||||
|  |             "Operator does not get in sync") | ||||||
|  |         leader = k8s.get_cluster_leader_pod() | ||||||
|  | 
 | ||||||
|  |         # produce wrong membership for cron_admin | ||||||
|  |         grant_dbowner = """ | ||||||
|  |             GRANT bar_owner TO cron_admin; | ||||||
|  |         """ | ||||||
|  |         self.query_database(leader.metadata.name, "postgres", grant_dbowner) | ||||||
|  | 
 | ||||||
|         # enable PostgresTeam CRD and lower resync |         # enable PostgresTeam CRD and lower resync | ||||||
|         owner_roles = { |         owner_roles = { | ||||||
|             "data": { |             "data": { | ||||||
|  | @ -175,16 +186,15 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, |         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, | ||||||
|                              "Operator does not get in sync") |                              "Operator does not get in sync") | ||||||
| 
 | 
 | ||||||
|         leader = k8s.get_cluster_leader_pod() |  | ||||||
|         owner_query = """ |         owner_query = """ | ||||||
|             SELECT a2.rolname |             SELECT a2.rolname | ||||||
|               FROM pg_catalog.pg_authid a |               FROM pg_catalog.pg_authid a | ||||||
|               JOIN pg_catalog.pg_auth_members am |               JOIN pg_catalog.pg_auth_members am | ||||||
|                 ON a.oid = am.member |                 ON a.oid = am.member | ||||||
|                AND a.rolname = 'cron_admin' |                AND a.rolname IN ('zalando', 'bar_owner', 'bar_data_owner') | ||||||
|               JOIN pg_catalog.pg_authid a2 |               JOIN pg_catalog.pg_authid a2 | ||||||
|                 ON a2.oid = am.roleid |                 ON a2.oid = am.roleid | ||||||
|              WHERE a2.rolname IN ('zalando', 'bar_owner', 'bar_data_owner'); |              WHERE a2.rolname = 'cron_admin'; | ||||||
|         """ |         """ | ||||||
|         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", owner_query)), 3, |         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", owner_query)), 3, | ||||||
|             "Not all additional users found in database", 10, 5) |             "Not all additional users found in database", 10, 5) | ||||||
|  |  | ||||||
|  | @ -134,7 +134,9 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres | ||||||
| 			Endpoints: make(map[PostgresRole]*v1.Endpoints)}, | 			Endpoints: make(map[PostgresRole]*v1.Endpoints)}, | ||||||
| 		userSyncStrategy: users.DefaultUserSyncStrategy{ | 		userSyncStrategy: users.DefaultUserSyncStrategy{ | ||||||
| 			PasswordEncryption:   passwordEncryption, | 			PasswordEncryption:   passwordEncryption, | ||||||
| 			RoleDeletionSuffix: cfg.OpConfig.RoleDeletionSuffix}, | 			RoleDeletionSuffix:   cfg.OpConfig.RoleDeletionSuffix, | ||||||
|  | 			AdditionalOwnerRoles: cfg.OpConfig.AdditionalOwnerRoles, | ||||||
|  | 		}, | ||||||
| 		deleteOptions:       metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy}, | 		deleteOptions:       metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy}, | ||||||
| 		podEventsQueue:      podEventsQueue, | 		podEventsQueue:      podEventsQueue, | ||||||
| 		KubeClient:          kubeClient, | 		KubeClient:          kubeClient, | ||||||
|  | @ -1308,28 +1310,15 @@ func (c *Cluster) initRobotUsers() error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) initAdditionalOwnerRoles() { | func (c *Cluster) initAdditionalOwnerRoles() { | ||||||
| 	for _, additionalOwner := range c.OpConfig.AdditionalOwnerRoles { | 	if len(c.OpConfig.AdditionalOwnerRoles) == 0 { | ||||||
| 		// fetch all database owners the additional should become a member of
 | 		return | ||||||
| 		memberOf := make([]string, 0) |  | ||||||
| 		for username, pgUser := range c.pgUsers { |  | ||||||
| 			if pgUser.IsDbOwner { |  | ||||||
| 				memberOf = append(memberOf, username) |  | ||||||
| 			} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		if len(memberOf) > 0 { | 	// fetch database owners and assign additional owner roles
 | ||||||
| 			namespace := c.Namespace | 	for username, pgUser := range c.pgUsers { | ||||||
| 			additionalOwnerPgUser := spec.PgUser{ | 		if pgUser.IsDbOwner { | ||||||
| 				Origin:    spec.RoleOriginSpilo, | 			pgUser.MemberOf = append(pgUser.MemberOf, c.OpConfig.AdditionalOwnerRoles...) | ||||||
| 				MemberOf:  memberOf, | 			c.pgUsers[username] = pgUser | ||||||
| 				Name:      additionalOwner, |  | ||||||
| 				Namespace: namespace, |  | ||||||
| 			} |  | ||||||
| 			if currentRole, present := c.pgUsers[additionalOwner]; present { |  | ||||||
| 				c.pgUsers[additionalOwner] = c.resolveNameConflict(¤tRole, &additionalOwnerPgUser) |  | ||||||
| 			} else { |  | ||||||
| 				c.pgUsers[additionalOwner] = additionalOwnerPgUser |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -148,11 +148,9 @@ func TestInitAdditionalOwnerRoles(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	manifestUsers := map[string]acidv1.UserFlags{"foo_owner": {}, "bar_owner": {}, "app_user": {}} | 	manifestUsers := map[string]acidv1.UserFlags{"foo_owner": {}, "bar_owner": {}, "app_user": {}} | ||||||
| 	expectedUsers := map[string]spec.PgUser{ | 	expectedUsers := map[string]spec.PgUser{ | ||||||
| 		"foo_owner":  {Origin: spec.RoleOriginManifest, Name: "foo_owner", Namespace: cl.Namespace, Password: "f123", Flags: []string{"LOGIN"}, IsDbOwner: true}, | 		"foo_owner": {Origin: spec.RoleOriginManifest, Name: "foo_owner", Namespace: cl.Namespace, Password: "f123", Flags: []string{"LOGIN"}, IsDbOwner: true, MemberOf: []string{"cron_admin", "part_man"}}, | ||||||
| 		"bar_owner":  {Origin: spec.RoleOriginManifest, Name: "bar_owner", Namespace: cl.Namespace, Password: "b123", Flags: []string{"LOGIN"}, IsDbOwner: true}, | 		"bar_owner": {Origin: spec.RoleOriginManifest, Name: "bar_owner", Namespace: cl.Namespace, Password: "b123", Flags: []string{"LOGIN"}, IsDbOwner: true, MemberOf: []string{"cron_admin", "part_man"}}, | ||||||
| 		"app_user":  {Origin: spec.RoleOriginManifest, Name: "app_user", Namespace: cl.Namespace, Password: "a123", Flags: []string{"LOGIN"}, IsDbOwner: false}, | 		"app_user":  {Origin: spec.RoleOriginManifest, Name: "app_user", Namespace: cl.Namespace, Password: "a123", Flags: []string{"LOGIN"}, IsDbOwner: false}, | ||||||
| 		"cron_admin": {Origin: spec.RoleOriginSpilo, Name: "cron_admin", Namespace: cl.Namespace, MemberOf: []string{"foo_owner", "bar_owner"}}, |  | ||||||
| 		"part_man":   {Origin: spec.RoleOriginSpilo, Name: "part_man", Namespace: cl.Namespace, MemberOf: []string{"foo_owner", "bar_owner"}}, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	cl.Spec.Databases = map[string]string{"foo_db": "foo_owner", "bar_db": "bar_owner"} | 	cl.Spec.Databases = map[string]string{"foo_db": "foo_owner", "bar_db": "bar_owner"} | ||||||
|  | @ -163,24 +161,15 @@ func TestInitAdditionalOwnerRoles(t *testing.T) { | ||||||
| 		t.Errorf("%s could not init manifest users", testName) | 		t.Errorf("%s could not init manifest users", testName) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// update passwords to compare with result
 | 	// now assign additional roles to owners
 | ||||||
| 	for manifestUser := range manifestUsers { |  | ||||||
| 		pgUser := cl.pgUsers[manifestUser] |  | ||||||
| 		pgUser.Password = manifestUser[0:1] + "123" |  | ||||||
| 		cl.pgUsers[manifestUser] = pgUser |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	cl.initAdditionalOwnerRoles() | 	cl.initAdditionalOwnerRoles() | ||||||
| 
 | 
 | ||||||
| 	for _, additionalOwnerRole := range cl.Config.OpConfig.AdditionalOwnerRoles { | 	// update passwords to compare with result
 | ||||||
| 		expectedPgUser := expectedUsers[additionalOwnerRole] | 	for username, existingPgUser := range cl.pgUsers { | ||||||
| 		existingPgUser, exists := cl.pgUsers[additionalOwnerRole] | 		expectedPgUser := expectedUsers[username] | ||||||
| 		if !exists { |  | ||||||
| 			t.Errorf("%s additional owner role %q not initilaized", testName, additionalOwnerRole) |  | ||||||
| 		} |  | ||||||
| 		if !util.IsEqualIgnoreOrder(expectedPgUser.MemberOf, existingPgUser.MemberOf) { | 		if !util.IsEqualIgnoreOrder(expectedPgUser.MemberOf, existingPgUser.MemberOf) { | ||||||
| 			t.Errorf("%s unexpected membership of additional owner role %q: expected member of %#v, got member of %#v", | 			t.Errorf("%s unexpected membership of user %q: expected member of %#v, got member of %#v", | ||||||
| 				testName, additionalOwnerRole, expectedPgUser.MemberOf, existingPgUser.MemberOf) | 				testName, username, expectedPgUser.MemberOf, existingPgUser.MemberOf) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1650,7 +1650,7 @@ func (c *Cluster) generateUserSecrets() map[string]*v1.Secret { | ||||||
| func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser) *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)
 | 	//Skip users with no password i.e. human users (they'll be authenticated using pam)
 | ||||||
| 	if pgUser.Password == "" { | 	if pgUser.Password == "" { | ||||||
| 		if pgUser.Origin != spec.RoleOriginTeamsAPI && pgUser.Origin != spec.RoleOriginSpilo { | 		if pgUser.Origin != spec.RoleOriginTeamsAPI { | ||||||
| 			c.logger.Warningf("could not generate secret for a non-teamsAPI role %q: role has no password", | 			c.logger.Warningf("could not generate secret for a non-teamsAPI role %q: role has no password", | ||||||
| 				pgUser.Name) | 				pgUser.Name) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -30,7 +30,6 @@ const ( | ||||||
| 	RoleOriginManifest | 	RoleOriginManifest | ||||||
| 	RoleOriginInfrastructure | 	RoleOriginInfrastructure | ||||||
| 	RoleOriginTeamsAPI | 	RoleOriginTeamsAPI | ||||||
| 	RoleOriginSpilo |  | ||||||
| 	RoleOriginSystem | 	RoleOriginSystem | ||||||
| 	RoleOriginBootstrap | 	RoleOriginBootstrap | ||||||
| 	RoleConnectionPooler | 	RoleConnectionPooler | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ const ( | ||||||
| 	alterRoleSetSQL      = `ALTER ROLE "%s" SET %s TO %s` | 	alterRoleSetSQL      = `ALTER ROLE "%s" SET %s TO %s` | ||||||
| 	dropUserSQL          = `SET LOCAL synchronous_commit = 'local'; DROP ROLE "%s";` | 	dropUserSQL          = `SET LOCAL synchronous_commit = 'local'; DROP ROLE "%s";` | ||||||
| 	grantToUserSQL       = `GRANT %s TO "%s"` | 	grantToUserSQL       = `GRANT %s TO "%s"` | ||||||
|  | 	revokeFromUserSQL    = `REVOKE %s FROM %s` | ||||||
| 	doBlockStmt          = `SET LOCAL synchronous_commit = 'local'; DO $$ BEGIN %s; END;$$;` | 	doBlockStmt          = `SET LOCAL synchronous_commit = 'local'; DO $$ BEGIN %s; END;$$;` | ||||||
| 	passwordTemplate     = "ENCRYPTED PASSWORD '%s'" | 	passwordTemplate     = "ENCRYPTED PASSWORD '%s'" | ||||||
| 	inRoleTemplate       = `IN ROLE %s` | 	inRoleTemplate       = `IN ROLE %s` | ||||||
|  | @ -33,6 +34,7 @@ const ( | ||||||
| type DefaultUserSyncStrategy struct { | type DefaultUserSyncStrategy struct { | ||||||
| 	PasswordEncryption   string | 	PasswordEncryption   string | ||||||
| 	RoleDeletionSuffix   string | 	RoleDeletionSuffix   string | ||||||
|  | 	AdditionalOwnerRoles []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.
 | ||||||
|  | @ -53,30 +55,27 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			r := spec.PgSyncUserRequest{} | 			r := spec.PgSyncUserRequest{} | ||||||
| 			r.User = dbUser |  | ||||||
| 			newMD5Password := util.NewEncryptor(strategy.PasswordEncryption).PGUserPassword(newUser) | 			newMD5Password := util.NewEncryptor(strategy.PasswordEncryption).PGUserPassword(newUser) | ||||||
| 
 | 
 | ||||||
| 			// do not compare for roles coming from docker image
 | 			// do not compare for roles coming from docker image
 | ||||||
| 			if newUser.Origin != spec.RoleOriginSpilo { |  | ||||||
| 			if dbUser.Password != newMD5Password { | 			if dbUser.Password != newMD5Password { | ||||||
| 				r.User.Password = newMD5Password | 				r.User.Password = newMD5Password | ||||||
| 				r.Kind = spec.PGsyncUserAlter | 				r.Kind = spec.PGsyncUserAlter | ||||||
| 			} | 			} | ||||||
|  | 			if addNewRoles, equal := util.SubstractStringSlices(newUser.MemberOf, dbUser.MemberOf); !equal { | ||||||
|  | 				r.User.MemberOf = addNewRoles | ||||||
|  | 				r.User.IsDbOwner = newUser.IsDbOwner | ||||||
|  | 				r.Kind = spec.PGsyncUserAlter | ||||||
|  | 			} | ||||||
| 			if addNewFlags, equal := util.SubstractStringSlices(newUser.Flags, dbUser.Flags); !equal { | 			if addNewFlags, equal := util.SubstractStringSlices(newUser.Flags, dbUser.Flags); !equal { | ||||||
| 				r.User.Flags = addNewFlags | 				r.User.Flags = addNewFlags | ||||||
| 				r.Kind = spec.PGsyncUserAlter | 				r.Kind = spec.PGsyncUserAlter | ||||||
| 			} | 			} | ||||||
| 			} |  | ||||||
| 			if addNewRoles, equal := util.SubstractStringSlices(newUser.MemberOf, dbUser.MemberOf); !equal { |  | ||||||
| 				r.User.MemberOf = addNewRoles |  | ||||||
| 				r.Kind = spec.PGsyncUserAlter |  | ||||||
| 			} |  | ||||||
| 			if r.Kind == spec.PGsyncUserAlter { | 			if r.Kind == spec.PGsyncUserAlter { | ||||||
| 				r.User.Name = newUser.Name | 				r.User.Name = newUser.Name | ||||||
| 				reqs = append(reqs, r) | 				reqs = append(reqs, r) | ||||||
| 			} | 			} | ||||||
| 			if newUser.Origin != spec.RoleOriginSpilo && | 			if len(newUser.Parameters) > 0 && | ||||||
| 				len(newUser.Parameters) > 0 && |  | ||||||
| 				!reflect.DeepEqual(dbUser.Parameters, newUser.Parameters) { | 				!reflect.DeepEqual(dbUser.Parameters, newUser.Parameters) { | ||||||
| 				reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncAlterSet, User: newUser}) | 				reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncAlterSet, User: newUser}) | ||||||
| 			} | 			} | ||||||
|  | @ -120,6 +119,15 @@ func (strategy DefaultUserSyncStrategy) ExecuteSyncRequests(requests []spec.PgSy | ||||||
| 			if err := strategy.alterPgUser(request.User, db); err != nil { | 			if err := strategy.alterPgUser(request.User, db); err != nil { | ||||||
| 				reqretries = append(reqretries, request) | 				reqretries = append(reqretries, request) | ||||||
| 				errors = append(errors, fmt.Sprintf("could not alter user %q: %v", request.User.Name, err)) | 				errors = append(errors, fmt.Sprintf("could not alter user %q: %v", request.User.Name, err)) | ||||||
|  | 				// XXX: we do not allow additional owner roles to be members of database owners
 | ||||||
|  | 				// if ALTER fails it could be because of the wrong memberhip (check #1862 for details)
 | ||||||
|  | 				// so in any case try to revoke the database owner from the additional owner roles
 | ||||||
|  | 				// the initial ALTER statement will be retried once and should work then
 | ||||||
|  | 				if request.User.IsDbOwner && len(strategy.AdditionalOwnerRoles) > 0 { | ||||||
|  | 					if err := resolveOwnerMembership(request.User, strategy.AdditionalOwnerRoles, db); err != nil { | ||||||
|  | 						errors = append(errors, fmt.Sprintf("could not resolve owner membership for %q: %v", request.User.Name, err)) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		case spec.PGSyncAlterSet: | 		case spec.PGSyncAlterSet: | ||||||
| 			if err := strategy.alterPgUserSet(request.User, db); err != nil { | 			if err := strategy.alterPgUserSet(request.User, db); err != nil { | ||||||
|  | @ -152,6 +160,21 @@ func (strategy DefaultUserSyncStrategy) ExecuteSyncRequests(requests []spec.PgSy | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func resolveOwnerMembership(dbOwner spec.PgUser, additionalOwners []string, db *sql.DB) error { | ||||||
|  | 	errors := make([]string, 0) | ||||||
|  | 	for _, additionalOwner := range additionalOwners { | ||||||
|  | 		if err := revokeRole(dbOwner.Name, additionalOwner, db); err != nil { | ||||||
|  | 			errors = append(errors, fmt.Sprintf("could not revoke %q from %q: %v", dbOwner.Name, additionalOwner, err)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(errors) > 0 { | ||||||
|  | 		return fmt.Errorf("could not resolve membership between %q and additional owner roles: %v", dbOwner.Name, strings.Join(errors, `', '`)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (strategy DefaultUserSyncStrategy) alterPgUserSet(user spec.PgUser, db *sql.DB) error { | func (strategy DefaultUserSyncStrategy) alterPgUserSet(user spec.PgUser, db *sql.DB) error { | ||||||
| 	queries := produceAlterRoleSetStmts(user) | 	queries := produceAlterRoleSetStmts(user) | ||||||
| 	query := fmt.Sprintf(doBlockStmt, strings.Join(queries, ";")) | 	query := fmt.Sprintf(doBlockStmt, strings.Join(queries, ";")) | ||||||
|  | @ -272,6 +295,16 @@ func quoteMemberList(user spec.PgUser) string { | ||||||
| 	return strings.Join(memberof, ",") | 	return strings.Join(memberof, ",") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func revokeRole(groupRole, role string, db *sql.DB) error { | ||||||
|  | 	revokeStmt := fmt.Sprintf(revokeFromUserSQL, groupRole, role) | ||||||
|  | 
 | ||||||
|  | 	if _, err := db.Exec(fmt.Sprintf(doBlockStmt, revokeStmt)); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // quoteVal quotes values to be used at ALTER ROLE SET param = value if necessary
 | // quoteVal quotes values to be used at ALTER ROLE SET param = value if necessary
 | ||||||
| func quoteParameterValue(name, val string) string { | func quoteParameterValue(name, val string) string { | ||||||
| 	start := val[0] | 	start := val[0] | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue