let isSystemUsername check all system users (#2489)
* let isSystemUsername check all system users * extend robot user unit test * reset system users for initSystemUser test
This commit is contained in:
		
							parent
							
								
									96077c47d6
								
							
						
					
					
						commit
						9ee14f26cb
					
				|  | @ -26,6 +26,8 @@ import ( | ||||||
| const ( | const ( | ||||||
| 	superUserName       = "postgres" | 	superUserName       = "postgres" | ||||||
| 	replicationUserName = "standby" | 	replicationUserName = "standby" | ||||||
|  | 	poolerUserName      = "pooler" | ||||||
|  | 	adminUserName       = "admin" | ||||||
| 	exampleSpiloConfig  = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"users":{"test":{"password":"","options":["CREATEDB","NOLOGIN"]}},"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"100","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}` | 	exampleSpiloConfig  = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"users":{"test":{"password":"","options":["CREATEDB","NOLOGIN"]}},"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"100","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}` | ||||||
| 	spiloConfigDiff     = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"users":{"test":{"password":"","options":["CREATEDB","NOLOGIN"]}},"dcs":{"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}` | 	spiloConfigDiff     = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"users":{"test":{"password":"","options":["CREATEDB","NOLOGIN"]}},"dcs":{"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}` | ||||||
| ) | ) | ||||||
|  | @ -37,7 +39,7 @@ var cl = New( | ||||||
| 	Config{ | 	Config{ | ||||||
| 		OpConfig: config.Config{ | 		OpConfig: config.Config{ | ||||||
| 			PodManagementPolicy: "ordered_ready", | 			PodManagementPolicy: "ordered_ready", | ||||||
| 			ProtectedRoles:      []string{"admin", "cron_admin", "part_man"}, | 			ProtectedRoles:      []string{adminUserName, "cron_admin", "part_man"}, | ||||||
| 			Auth: config.Auth{ | 			Auth: config.Auth{ | ||||||
| 				SuperUsername:        superUserName, | 				SuperUsername:        superUserName, | ||||||
| 				ReplicationUsername:  replicationUserName, | 				ReplicationUsername:  replicationUserName, | ||||||
|  | @ -46,6 +48,9 @@ var cl = New( | ||||||
| 			Resources: config.Resources{ | 			Resources: config.Resources{ | ||||||
| 				DownscalerAnnotations: []string{"downscaler/*"}, | 				DownscalerAnnotations: []string{"downscaler/*"}, | ||||||
| 			}, | 			}, | ||||||
|  | 			ConnectionPooler: config.ConnectionPooler{ | ||||||
|  | 				User: poolerUserName, | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	k8sutil.NewMockKubernetesClient(), | 	k8sutil.NewMockKubernetesClient(), | ||||||
|  | @ -55,6 +60,20 @@ var cl = New( | ||||||
| 			Namespace:   "test", | 			Namespace:   "test", | ||||||
| 			Annotations: map[string]string{"downscaler/downtime_replicas": "0"}, | 			Annotations: map[string]string{"downscaler/downtime_replicas": "0"}, | ||||||
| 		}, | 		}, | ||||||
|  | 		Spec: acidv1.PostgresSpec{ | ||||||
|  | 			EnableConnectionPooler: util.True(), | ||||||
|  | 			Streams: []acidv1.Stream{ | ||||||
|  | 				acidv1.Stream{ | ||||||
|  | 					ApplicationId: "test-app", | ||||||
|  | 					Database:      "test_db", | ||||||
|  | 					Tables: map[string]acidv1.StreamTable{ | ||||||
|  | 						"test_table": acidv1.StreamTable{ | ||||||
|  | 							EventType: "test-app.test", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	logger, | 	logger, | ||||||
| 	eventRecorder, | 	eventRecorder, | ||||||
|  | @ -127,56 +146,85 @@ func TestStatefulSetUpdateWithEnv(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestInitRobotUsers(t *testing.T) { | func TestInitRobotUsers(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
|  | 		testCase      string | ||||||
| 		manifestUsers map[string]acidv1.UserFlags | 		manifestUsers map[string]acidv1.UserFlags | ||||||
| 		infraRoles    map[string]spec.PgUser | 		infraRoles    map[string]spec.PgUser | ||||||
| 		result        map[string]spec.PgUser | 		result        map[string]spec.PgUser | ||||||
| 		err           error | 		err           error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
|  | 			testCase:      "manifest user called like infrastructure role - latter should take percedence", | ||||||
| 			manifestUsers: map[string]acidv1.UserFlags{"foo": {"superuser", "createdb"}}, | 			manifestUsers: map[string]acidv1.UserFlags{"foo": {"superuser", "createdb"}}, | ||||||
| 			infraRoles:    map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Namespace: cl.Namespace, Password: "bar"}}, | 			infraRoles:    map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Namespace: cl.Namespace, Password: "bar"}}, | ||||||
| 			result:        map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Namespace: cl.Namespace, Password: "bar"}}, | 			result:        map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Namespace: cl.Namespace, Password: "bar"}}, | ||||||
| 			err:           nil, | 			err:           nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | 			testCase:      "manifest user with forbidden characters", | ||||||
| 			manifestUsers: map[string]acidv1.UserFlags{"!fooBar": {"superuser", "createdb"}}, | 			manifestUsers: map[string]acidv1.UserFlags{"!fooBar": {"superuser", "createdb"}}, | ||||||
| 			err:           fmt.Errorf(`invalid username: "!fooBar"`), | 			err:           fmt.Errorf(`invalid username: "!fooBar"`), | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | 			testCase:      "manifest user with unknown privileges (should be catched by CRD, too)", | ||||||
| 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"!superuser", "createdb"}}, | 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"!superuser", "createdb"}}, | ||||||
| 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | ||||||
| 				`user flag "!superuser" is not alphanumeric`), | 				`user flag "!superuser" is not alphanumeric`), | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | 			testCase:      "manifest user with unknown privileges - part 2 (should be catched by CRD, too)", | ||||||
| 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"superuser1", "createdb"}}, | 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"superuser1", "createdb"}}, | ||||||
| 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | ||||||
| 				`user flag "SUPERUSER1" is not valid`), | 				`user flag "SUPERUSER1" is not valid`), | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | 			testCase:      "manifest user with conflicting flags", | ||||||
| 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"inherit", "noinherit"}}, | 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"inherit", "noinherit"}}, | ||||||
| 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | ||||||
| 				`conflicting user flags: "NOINHERIT" and "INHERIT"`), | 				`conflicting user flags: "NOINHERIT" and "INHERIT"`), | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			manifestUsers: map[string]acidv1.UserFlags{"admin": {"superuser"}, superUserName: {"createdb"}}, | 			testCase:      "manifest user called like Spilo system users", | ||||||
|  | 			manifestUsers: map[string]acidv1.UserFlags{superUserName: {"createdb"}, replicationUserName: {"replication"}}, | ||||||
|  | 			infraRoles:    map[string]spec.PgUser{}, | ||||||
|  | 			result:        map[string]spec.PgUser{}, | ||||||
|  | 			err:           nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			testCase:      "manifest user called like protected user name", | ||||||
|  | 			manifestUsers: map[string]acidv1.UserFlags{adminUserName: {"superuser"}}, | ||||||
|  | 			infraRoles:    map[string]spec.PgUser{}, | ||||||
|  | 			result:        map[string]spec.PgUser{}, | ||||||
|  | 			err:           nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			testCase:      "manifest user called like pooler system user", | ||||||
|  | 			manifestUsers: map[string]acidv1.UserFlags{poolerUserName: {}}, | ||||||
|  | 			infraRoles:    map[string]spec.PgUser{}, | ||||||
|  | 			result:        map[string]spec.PgUser{}, | ||||||
|  | 			err:           nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			testCase:      "manifest user called like stream system user", | ||||||
|  | 			manifestUsers: map[string]acidv1.UserFlags{"fes_user": {"replication"}}, | ||||||
| 			infraRoles:    map[string]spec.PgUser{}, | 			infraRoles:    map[string]spec.PgUser{}, | ||||||
| 			result:        map[string]spec.PgUser{}, | 			result:        map[string]spec.PgUser{}, | ||||||
| 			err:           nil, | 			err:           nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | 	cl.initSystemUsers() | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
| 		cl.Spec.Users = tt.manifestUsers | 		cl.Spec.Users = tt.manifestUsers | ||||||
| 		cl.pgUsers = tt.infraRoles | 		cl.pgUsers = tt.infraRoles | ||||||
| 		if err := cl.initRobotUsers(); err != nil { | 		if err := cl.initRobotUsers(); err != nil { | ||||||
| 			if tt.err == nil { | 			if tt.err == nil { | ||||||
| 				t.Errorf("%s got an unexpected error: %v", t.Name(), err) | 				t.Errorf("%s - %s: got an unexpected error: %v", tt.testCase, t.Name(), err) | ||||||
| 			} | 			} | ||||||
| 			if err.Error() != tt.err.Error() { | 			if err.Error() != tt.err.Error() { | ||||||
| 				t.Errorf("%s expected error %v, got %v", t.Name(), tt.err, err) | 				t.Errorf("%s - %s: expected error %v, got %v", tt.testCase, t.Name(), tt.err, err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			if !reflect.DeepEqual(cl.pgUsers, tt.result) { | 			if !reflect.DeepEqual(cl.pgUsers, tt.result) { | ||||||
| 				t.Errorf("%s expected: %#v, got %#v", t.Name(), tt.result, cl.pgUsers) | 				t.Errorf("%s - %s: expected: %#v, got %#v", tt.testCase, t.Name(), tt.result, cl.pgUsers) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -269,7 +317,7 @@ func TestInitHumanUsers(t *testing.T) { | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			existingRoles: map[string]spec.PgUser{}, | 			existingRoles: map[string]spec.PgUser{}, | ||||||
| 			teamRoles:     []string{"admin", replicationUserName}, | 			teamRoles:     []string{adminUserName, replicationUserName}, | ||||||
| 			result:        map[string]spec.PgUser{}, | 			result:        map[string]spec.PgUser{}, | ||||||
| 			err:           nil, | 			err:           nil, | ||||||
| 		}, | 		}, | ||||||
|  | @ -896,6 +944,11 @@ func TestServiceAnnotations(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestInitSystemUsers(t *testing.T) { | func TestInitSystemUsers(t *testing.T) { | ||||||
|  | 	// reset system users, pooler and stream section
 | ||||||
|  | 	cl.systemUsers = make(map[string]spec.PgUser) | ||||||
|  | 	cl.Spec.EnableConnectionPooler = boolToPointer(false) | ||||||
|  | 	cl.Spec.Streams = []acidv1.Stream{} | ||||||
|  | 
 | ||||||
| 	// default cluster without connection pooler and event streams
 | 	// default cluster without connection pooler and event streams
 | ||||||
| 	cl.initSystemUsers() | 	cl.initSystemUsers() | ||||||
| 	if _, exist := cl.systemUsers[constants.ConnectionPoolerUserKeyName]; exist { | 	if _, exist := cl.systemUsers[constants.ConnectionPoolerUserKeyName]; exist { | ||||||
|  | @ -914,35 +967,35 @@ func TestInitSystemUsers(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	// superuser is not allowed as connection pool user
 | 	// superuser is not allowed as connection pool user
 | ||||||
| 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | ||||||
| 		User: "postgres", | 		User: superUserName, | ||||||
| 	} | 	} | ||||||
| 	cl.OpConfig.SuperUsername = "postgres" | 	cl.OpConfig.SuperUsername = superUserName | ||||||
| 	cl.OpConfig.ConnectionPooler.User = "pooler" | 	cl.OpConfig.ConnectionPooler.User = poolerUserName | ||||||
| 
 | 
 | ||||||
| 	cl.initSystemUsers() | 	cl.initSystemUsers() | ||||||
| 	if _, exist := cl.systemUsers["pooler"]; !exist { | 	if _, exist := cl.systemUsers[poolerUserName]; !exist { | ||||||
| 		t.Errorf("%s, Superuser is not allowed to be a connection pool user", t.Name()) | 		t.Errorf("%s, Superuser is not allowed to be a connection pool user", t.Name()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// neither protected users are
 | 	// neither protected users are
 | ||||||
| 	delete(cl.systemUsers, "pooler") | 	delete(cl.systemUsers, poolerUserName) | ||||||
| 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | ||||||
| 		User: "admin", | 		User: adminUserName, | ||||||
| 	} | 	} | ||||||
| 	cl.OpConfig.ProtectedRoles = []string{"admin"} | 	cl.OpConfig.ProtectedRoles = []string{adminUserName} | ||||||
| 
 | 
 | ||||||
| 	cl.initSystemUsers() | 	cl.initSystemUsers() | ||||||
| 	if _, exist := cl.systemUsers["pooler"]; !exist { | 	if _, exist := cl.systemUsers[poolerUserName]; !exist { | ||||||
| 		t.Errorf("%s, Protected user are not allowed to be a connection pool user", t.Name()) | 		t.Errorf("%s, Protected user are not allowed to be a connection pool user", t.Name()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	delete(cl.systemUsers, "pooler") | 	delete(cl.systemUsers, poolerUserName) | ||||||
| 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | ||||||
| 		User: "standby", | 		User: replicationUserName, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	cl.initSystemUsers() | 	cl.initSystemUsers() | ||||||
| 	if _, exist := cl.systemUsers["pooler"]; !exist { | 	if _, exist := cl.systemUsers[poolerUserName]; !exist { | ||||||
| 		t.Errorf("%s, System users are not allowed to be a connection pool user", t.Name()) | 		t.Errorf("%s, System users are not allowed to be a connection pool user", t.Name()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -960,8 +1013,8 @@ func TestInitSystemUsers(t *testing.T) { | ||||||
| 			ApplicationId: "test-app", | 			ApplicationId: "test-app", | ||||||
| 			Database:      "test_db", | 			Database:      "test_db", | ||||||
| 			Tables: map[string]acidv1.StreamTable{ | 			Tables: map[string]acidv1.StreamTable{ | ||||||
| 				"data.test_table": { | 				"test_table": { | ||||||
| 					EventType: "test_event", | 					EventType: "test-app.test", | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -1017,7 +1070,7 @@ func TestPreparedDatabases(t *testing.T) { | ||||||
| 			subTest:  "Test admin role of owner", | 			subTest:  "Test admin role of owner", | ||||||
| 			role:     "foo_owner", | 			role:     "foo_owner", | ||||||
| 			memberOf: "", | 			memberOf: "", | ||||||
| 			admin:    "admin", | 			admin:    adminUserName, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "Test writer is a member of reader", | 			subTest:  "Test writer is a member of reader", | ||||||
|  |  | ||||||
|  | @ -78,7 +78,14 @@ func (c *Cluster) isProtectedUsername(username string) bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) isSystemUsername(username string) bool { | func (c *Cluster) isSystemUsername(username string) bool { | ||||||
| 	return (username == c.OpConfig.SuperUsername || username == c.OpConfig.ReplicationUsername) | 	// is there a pooler system user defined
 | ||||||
|  | 	for _, systemUser := range c.systemUsers { | ||||||
|  | 		if username == systemUser.Name { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func isValidFlag(flag string) bool { | func isValidFlag(flag string) bool { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue