321 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
package cluster
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
	acidv1 "github.com/zalando-incubator/postgres-operator/pkg/apis/acid.zalan.do/v1"
 | 
						|
	"github.com/zalando-incubator/postgres-operator/pkg/spec"
 | 
						|
	"github.com/zalando-incubator/postgres-operator/pkg/util/config"
 | 
						|
	"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
 | 
						|
	"github.com/zalando-incubator/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)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |