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 ( | ||||
| 	superUserName       = "postgres" | ||||
| 	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"}}}}}` | ||||
| 	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{ | ||||
| 		OpConfig: config.Config{ | ||||
| 			PodManagementPolicy: "ordered_ready", | ||||
| 			ProtectedRoles:      []string{"admin", "cron_admin", "part_man"}, | ||||
| 			ProtectedRoles:      []string{adminUserName, "cron_admin", "part_man"}, | ||||
| 			Auth: config.Auth{ | ||||
| 				SuperUsername:        superUserName, | ||||
| 				ReplicationUsername:  replicationUserName, | ||||
|  | @ -46,6 +48,9 @@ var cl = New( | |||
| 			Resources: config.Resources{ | ||||
| 				DownscalerAnnotations: []string{"downscaler/*"}, | ||||
| 			}, | ||||
| 			ConnectionPooler: config.ConnectionPooler{ | ||||
| 				User: poolerUserName, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	k8sutil.NewMockKubernetesClient(), | ||||
|  | @ -55,6 +60,20 @@ var cl = New( | |||
| 			Namespace:   "test", | ||||
| 			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, | ||||
| 	eventRecorder, | ||||
|  | @ -127,56 +146,85 @@ func TestStatefulSetUpdateWithEnv(t *testing.T) { | |||
| 
 | ||||
| func TestInitRobotUsers(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		testCase      string | ||||
| 		manifestUsers map[string]acidv1.UserFlags | ||||
| 		infraRoles    map[string]spec.PgUser | ||||
| 		result        map[string]spec.PgUser | ||||
| 		err           error | ||||
| 	}{ | ||||
| 		{ | ||||
| 			testCase:      "manifest user called like infrastructure role - latter should take percedence", | ||||
| 			manifestUsers: map[string]acidv1.UserFlags{"foo": {"superuser", "createdb"}}, | ||||
| 			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"}}, | ||||
| 			err:           nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			testCase:      "manifest user with forbidden characters", | ||||
| 			manifestUsers: map[string]acidv1.UserFlags{"!fooBar": {"superuser", "createdb"}}, | ||||
| 			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"}}, | ||||
| 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | ||||
| 				`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"}}, | ||||
| 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | ||||
| 				`user flag "SUPERUSER1" is not valid`), | ||||
| 		}, | ||||
| 		{ | ||||
| 			testCase:      "manifest user with conflicting flags", | ||||
| 			manifestUsers: map[string]acidv1.UserFlags{"foobar": {"inherit", "noinherit"}}, | ||||
| 			err: fmt.Errorf(`invalid flags for user "foobar": ` + | ||||
| 				`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{}, | ||||
| 			result:        map[string]spec.PgUser{}, | ||||
| 			err:           nil, | ||||
| 		}, | ||||
| 	} | ||||
| 	cl.initSystemUsers() | ||||
| 	for _, tt := range tests { | ||||
| 		cl.Spec.Users = tt.manifestUsers | ||||
| 		cl.pgUsers = tt.infraRoles | ||||
| 		if err := cl.initRobotUsers(); 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() { | ||||
| 				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 { | ||||
| 			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{}, | ||||
| 			teamRoles:     []string{"admin", replicationUserName}, | ||||
| 			teamRoles:     []string{adminUserName, replicationUserName}, | ||||
| 			result:        map[string]spec.PgUser{}, | ||||
| 			err:           nil, | ||||
| 		}, | ||||
|  | @ -896,6 +944,11 @@ func TestServiceAnnotations(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
 | ||||
| 	cl.initSystemUsers() | ||||
| 	if _, exist := cl.systemUsers[constants.ConnectionPoolerUserKeyName]; exist { | ||||
|  | @ -914,35 +967,35 @@ func TestInitSystemUsers(t *testing.T) { | |||
| 
 | ||||
| 	// superuser is not allowed as connection pool user
 | ||||
| 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | ||||
| 		User: "postgres", | ||||
| 		User: superUserName, | ||||
| 	} | ||||
| 	cl.OpConfig.SuperUsername = "postgres" | ||||
| 	cl.OpConfig.ConnectionPooler.User = "pooler" | ||||
| 	cl.OpConfig.SuperUsername = superUserName | ||||
| 	cl.OpConfig.ConnectionPooler.User = poolerUserName | ||||
| 
 | ||||
| 	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()) | ||||
| 	} | ||||
| 
 | ||||
| 	// neither protected users are
 | ||||
| 	delete(cl.systemUsers, "pooler") | ||||
| 	delete(cl.systemUsers, poolerUserName) | ||||
| 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | ||||
| 		User: "admin", | ||||
| 		User: adminUserName, | ||||
| 	} | ||||
| 	cl.OpConfig.ProtectedRoles = []string{"admin"} | ||||
| 	cl.OpConfig.ProtectedRoles = []string{adminUserName} | ||||
| 
 | ||||
| 	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()) | ||||
| 	} | ||||
| 
 | ||||
| 	delete(cl.systemUsers, "pooler") | ||||
| 	delete(cl.systemUsers, poolerUserName) | ||||
| 	cl.Spec.ConnectionPooler = &acidv1.ConnectionPooler{ | ||||
| 		User: "standby", | ||||
| 		User: replicationUserName, | ||||
| 	} | ||||
| 
 | ||||
| 	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()) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -960,8 +1013,8 @@ func TestInitSystemUsers(t *testing.T) { | |||
| 			ApplicationId: "test-app", | ||||
| 			Database:      "test_db", | ||||
| 			Tables: map[string]acidv1.StreamTable{ | ||||
| 				"data.test_table": { | ||||
| 					EventType: "test_event", | ||||
| 				"test_table": { | ||||
| 					EventType: "test-app.test", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
|  | @ -1017,7 +1070,7 @@ func TestPreparedDatabases(t *testing.T) { | |||
| 			subTest:  "Test admin role of owner", | ||||
| 			role:     "foo_owner", | ||||
| 			memberOf: "", | ||||
| 			admin:    "admin", | ||||
| 			admin:    adminUserName, | ||||
| 		}, | ||||
| 		{ | ||||
| 			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 { | ||||
| 	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 { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue