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:
Felix Kunde 2023-12-08 15:21:56 +01:00 committed by GitHub
parent 96077c47d6
commit 9ee14f26cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 21 deletions

View File

@ -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",

View File

@ -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 {