321 lines
9.3 KiB
Go
321 lines
9.3 KiB
Go
package cluster
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
|
"github.com/zalando/postgres-operator/pkg/spec"
|
|
"github.com/zalando/postgres-operator/pkg/util/config"
|
|
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
|
"github.com/zalando/postgres-operator/pkg/util/teams"
|
|
"k8s.io/api/core/v1"
|
|
)
|
|
|
|
const (
|
|
superUserName = "postgres"
|
|
replicationUserName = "standby"
|
|
)
|
|
|
|
var logger = logrus.New().WithField("test", "cluster")
|
|
var cl = New(Config{OpConfig: config.Config{ProtectedRoles: []string{"admin"},
|
|
Auth: config.Auth{SuperUsername: superUserName,
|
|
ReplicationUsername: replicationUserName}}},
|
|
k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger)
|
|
|
|
func TestInitRobotUsers(t *testing.T) {
|
|
testName := "TestInitRobotUsers"
|
|
tests := []struct {
|
|
manifestUsers map[string]acidv1.UserFlags
|
|
infraRoles map[string]spec.PgUser
|
|
result map[string]spec.PgUser
|
|
err error
|
|
}{
|
|
{
|
|
manifestUsers: map[string]acidv1.UserFlags{"foo": {"superuser", "createdb"}},
|
|
infraRoles: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Password: "bar"}},
|
|
result: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Password: "bar"}},
|
|
err: nil,
|
|
},
|
|
{
|
|
manifestUsers: map[string]acidv1.UserFlags{"!fooBar": {"superuser", "createdb"}},
|
|
err: fmt.Errorf(`invalid username: "!fooBar"`),
|
|
},
|
|
{
|
|
manifestUsers: map[string]acidv1.UserFlags{"foobar": {"!superuser", "createdb"}},
|
|
err: fmt.Errorf(`invalid flags for user "foobar": ` +
|
|
`user flag "!superuser" is not alphanumeric`),
|
|
},
|
|
{
|
|
manifestUsers: map[string]acidv1.UserFlags{"foobar": {"superuser1", "createdb"}},
|
|
err: fmt.Errorf(`invalid flags for user "foobar": ` +
|
|
`user flag "SUPERUSER1" is not valid`),
|
|
},
|
|
{
|
|
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"}},
|
|
infraRoles: map[string]spec.PgUser{},
|
|
result: map[string]spec.PgUser{},
|
|
err: nil,
|
|
},
|
|
}
|
|
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", testName, err)
|
|
}
|
|
if err.Error() != tt.err.Error() {
|
|
t.Errorf("%s expected error %v, got %v", testName, tt.err, err)
|
|
}
|
|
} else {
|
|
if !reflect.DeepEqual(cl.pgUsers, tt.result) {
|
|
t.Errorf("%s expected: %#v, got %#v", testName, tt.result, cl.pgUsers)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type mockOAuthTokenGetter struct {
|
|
}
|
|
|
|
func (m *mockOAuthTokenGetter) getOAuthToken() (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
type mockTeamsAPIClient struct {
|
|
members []string
|
|
}
|
|
|
|
func (m *mockTeamsAPIClient) TeamInfo(teamID, token string) (tm *teams.Team, err error) {
|
|
return &teams.Team{Members: m.members}, nil
|
|
}
|
|
|
|
func (m *mockTeamsAPIClient) setMembers(members []string) {
|
|
m.members = members
|
|
}
|
|
|
|
// Test adding a member of a product team owning a particular DB cluster
|
|
func TestInitHumanUsers(t *testing.T) {
|
|
|
|
var mockTeamsAPI mockTeamsAPIClient
|
|
cl.oauthTokenGetter = &mockOAuthTokenGetter{}
|
|
cl.teamsAPIClient = &mockTeamsAPI
|
|
testName := "TestInitHumanUsers"
|
|
|
|
// members of a product team are granted superuser rights for DBs of their team
|
|
cl.OpConfig.EnableTeamSuperuser = true
|
|
|
|
cl.OpConfig.EnableTeamsAPI = true
|
|
cl.OpConfig.PamRoleName = "zalandos"
|
|
cl.Spec.TeamID = "test"
|
|
|
|
tests := []struct {
|
|
existingRoles map[string]spec.PgUser
|
|
teamRoles []string
|
|
result map[string]spec.PgUser
|
|
}{
|
|
{
|
|
existingRoles: map[string]spec.PgUser{"foo": {Name: "foo", Origin: spec.RoleOriginTeamsAPI,
|
|
Flags: []string{"NOLOGIN"}}, "bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
|
|
teamRoles: []string{"foo"},
|
|
result: map[string]spec.PgUser{"foo": {Name: "foo", Origin: spec.RoleOriginTeamsAPI,
|
|
MemberOf: []string{cl.OpConfig.PamRoleName}, Flags: []string{"LOGIN", "SUPERUSER"}},
|
|
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
|
|
},
|
|
{
|
|
existingRoles: map[string]spec.PgUser{},
|
|
teamRoles: []string{"admin", replicationUserName},
|
|
result: map[string]spec.PgUser{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
cl.pgUsers = tt.existingRoles
|
|
mockTeamsAPI.setMembers(tt.teamRoles)
|
|
if err := cl.initHumanUsers(); err != nil {
|
|
t.Errorf("%s got an unexpected error %v", testName, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(cl.pgUsers, tt.result) {
|
|
t.Errorf("%s expects %#v, got %#v", testName, tt.result, cl.pgUsers)
|
|
}
|
|
}
|
|
}
|
|
|
|
type mockTeam struct {
|
|
teamID string
|
|
members []string
|
|
isPostgresSuperuserTeam bool
|
|
}
|
|
|
|
type mockTeamsAPIClientMultipleTeams struct {
|
|
teams []mockTeam
|
|
}
|
|
|
|
func (m *mockTeamsAPIClientMultipleTeams) TeamInfo(teamID, token string) (tm *teams.Team, err error) {
|
|
for _, team := range m.teams {
|
|
if team.teamID == teamID {
|
|
return &teams.Team{Members: team.members}, nil
|
|
}
|
|
}
|
|
|
|
// should not be reached if a slice with teams is populated correctly
|
|
return nil, nil
|
|
}
|
|
|
|
// Test adding members of maintenance teams that get superuser rights for all PG databases
|
|
func TestInitHumanUsersWithSuperuserTeams(t *testing.T) {
|
|
|
|
var mockTeamsAPI mockTeamsAPIClientMultipleTeams
|
|
cl.oauthTokenGetter = &mockOAuthTokenGetter{}
|
|
cl.teamsAPIClient = &mockTeamsAPI
|
|
cl.OpConfig.EnableTeamSuperuser = false
|
|
testName := "TestInitHumanUsersWithSuperuserTeams"
|
|
|
|
cl.OpConfig.EnableTeamsAPI = true
|
|
cl.OpConfig.PamRoleName = "zalandos"
|
|
|
|
teamA := mockTeam{
|
|
teamID: "postgres_superusers",
|
|
members: []string{"postgres_superuser"},
|
|
isPostgresSuperuserTeam: true,
|
|
}
|
|
|
|
userA := spec.PgUser{
|
|
Name: "postgres_superuser",
|
|
Origin: spec.RoleOriginTeamsAPI,
|
|
MemberOf: []string{cl.OpConfig.PamRoleName},
|
|
Flags: []string{"LOGIN", "SUPERUSER"},
|
|
}
|
|
|
|
teamB := mockTeam{
|
|
teamID: "postgres_admins",
|
|
members: []string{"postgres_admin"},
|
|
isPostgresSuperuserTeam: true,
|
|
}
|
|
|
|
userB := spec.PgUser{
|
|
Name: "postgres_admin",
|
|
Origin: spec.RoleOriginTeamsAPI,
|
|
MemberOf: []string{cl.OpConfig.PamRoleName},
|
|
Flags: []string{"LOGIN", "SUPERUSER"},
|
|
}
|
|
|
|
teamTest := mockTeam{
|
|
teamID: "test",
|
|
members: []string{"test_user"},
|
|
isPostgresSuperuserTeam: false,
|
|
}
|
|
|
|
userTest := spec.PgUser{
|
|
Name: "test_user",
|
|
Origin: spec.RoleOriginTeamsAPI,
|
|
MemberOf: []string{cl.OpConfig.PamRoleName},
|
|
Flags: []string{"LOGIN"},
|
|
}
|
|
|
|
tests := []struct {
|
|
ownerTeam string
|
|
existingRoles map[string]spec.PgUser
|
|
superuserTeams []string
|
|
teams []mockTeam
|
|
result map[string]spec.PgUser
|
|
}{
|
|
// case 1: there are two different teams of PG maintainers and one product team
|
|
{
|
|
ownerTeam: "test",
|
|
existingRoles: map[string]spec.PgUser{},
|
|
superuserTeams: []string{"postgres_superusers", "postgres_admins"},
|
|
teams: []mockTeam{teamA, teamB, teamTest},
|
|
result: map[string]spec.PgUser{
|
|
"postgres_superuser": userA,
|
|
"postgres_admin": userB,
|
|
"test_user": userTest,
|
|
},
|
|
},
|
|
// case 2: the team of superusers creates a new PG cluster
|
|
{
|
|
ownerTeam: "postgres_superusers",
|
|
existingRoles: map[string]spec.PgUser{},
|
|
superuserTeams: []string{"postgres_superusers"},
|
|
teams: []mockTeam{teamA},
|
|
result: map[string]spec.PgUser{
|
|
"postgres_superuser": userA,
|
|
},
|
|
},
|
|
// case 3: the team owning the cluster is promoted to the maintainers' status
|
|
{
|
|
ownerTeam: "postgres_superusers",
|
|
existingRoles: map[string]spec.PgUser{
|
|
// role with the name exists before w/o superuser privilege
|
|
"postgres_superuser": {
|
|
Origin: spec.RoleOriginTeamsAPI,
|
|
Name: "postgres_superuser",
|
|
Password: "",
|
|
Flags: []string{"LOGIN"},
|
|
MemberOf: []string{cl.OpConfig.PamRoleName},
|
|
Parameters: map[string]string(nil)}},
|
|
superuserTeams: []string{"postgres_superusers"},
|
|
teams: []mockTeam{teamA},
|
|
result: map[string]spec.PgUser{
|
|
"postgres_superuser": userA,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
mockTeamsAPI.teams = tt.teams
|
|
|
|
cl.Spec.TeamID = tt.ownerTeam
|
|
cl.pgUsers = tt.existingRoles
|
|
cl.OpConfig.PostgresSuperuserTeams = tt.superuserTeams
|
|
|
|
if err := cl.initHumanUsers(); err != nil {
|
|
t.Errorf("%s got an unexpected error %v", testName, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(cl.pgUsers, tt.result) {
|
|
t.Errorf("%s expects %#v, got %#v", testName, tt.result, cl.pgUsers)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestShouldDeleteSecret(t *testing.T) {
|
|
testName := "TestShouldDeleteSecret"
|
|
|
|
tests := []struct {
|
|
secret *v1.Secret
|
|
outcome bool
|
|
}{
|
|
{
|
|
secret: &v1.Secret{Data: map[string][]byte{"username": []byte("foobar")}},
|
|
outcome: true,
|
|
},
|
|
{
|
|
secret: &v1.Secret{Data: map[string][]byte{"username": []byte(superUserName)}},
|
|
|
|
outcome: false,
|
|
},
|
|
{
|
|
secret: &v1.Secret{Data: map[string][]byte{"username": []byte(replicationUserName)}},
|
|
outcome: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if outcome, username := cl.shouldDeleteSecret(tt.secret); outcome != tt.outcome {
|
|
t.Errorf("%s expects the check for deletion of the username %q secret to return %t, got %t",
|
|
testName, username, tt.outcome, outcome)
|
|
}
|
|
}
|
|
}
|