[WIP] Grant 'superuser' to the members of Postgres admin teams (#371)

Added support for superuser team in addition to the admin team that owns the postgres cluster.
This commit is contained in:
zerg-junior
2018-08-30 10:51:37 +02:00
committed by Jan Mussler
parent 1e53e22773
commit 25fa45fd58
10 changed files with 211 additions and 13 deletions
@@ -3,9 +3,10 @@ package v1
import (
"github.com/zalando-incubator/postgres-operator/pkg/util/config"
"time"
"github.com/zalando-incubator/postgres-operator/pkg/spec"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"time"
)
// +genclient
@@ -99,6 +100,7 @@ type TeamsAPIConfiguration struct {
PamRoleName string `json:"pam_role_name,omitempty"`
PamConfiguration string `json:"pam_configuration,omitempty"`
ProtectedRoles []string `json:"protected_role_names,omitempty"`
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
}
type LoggingRESTAPIConfiguration struct {
@@ -631,6 +631,11 @@ func (in *TeamsAPIConfiguration) DeepCopyInto(out *TeamsAPIConfiguration) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.PostgresSuperuserTeams != nil {
in, out := &in.PostgresSuperuserTeams, &out.PostgresSuperuserTeams
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
+33 -4
View File
@@ -723,11 +723,13 @@ func (c *Cluster) initRobotUsers() error {
return nil
}
func (c *Cluster) initHumanUsers() error {
teamMembers, err := c.getTeamMembers()
func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) error {
teamMembers, err := c.getTeamMembers(teamID)
if err != nil {
return fmt.Errorf("could not get list of team members: %v", err)
return fmt.Errorf("could not get list of team members for team %q: %v", teamID, err)
}
for _, username := range teamMembers {
flags := []string{constants.RoleFlagLogin}
memberOf := []string{c.OpConfig.PamRoleName}
@@ -735,7 +737,7 @@ func (c *Cluster) initHumanUsers() error {
if c.shouldAvoidProtectedOrSystemRole(username, "API role") {
continue
}
if c.OpConfig.EnableTeamSuperuser {
if c.OpConfig.EnableTeamSuperuser || isPostgresSuperuserTeam {
flags = append(flags, constants.RoleFlagSuperuser)
} else {
if c.OpConfig.TeamAdminRole != "" {
@@ -761,6 +763,33 @@ func (c *Cluster) initHumanUsers() error {
return nil
}
func (c *Cluster) initHumanUsers() error {
var clusterIsOwnedBySuperuserTeam bool
for _, postgresSuperuserTeam := range c.OpConfig.PostgresSuperuserTeams {
err := c.initTeamMembers(postgresSuperuserTeam, true)
if err != nil {
return fmt.Errorf("Cannot create a team %q of Postgres superusers: %v", postgresSuperuserTeam, err)
}
if postgresSuperuserTeam == c.Spec.TeamID {
clusterIsOwnedBySuperuserTeam = true
}
}
if clusterIsOwnedBySuperuserTeam {
c.logger.Infof("Team %q owning the cluster is also a team of superusers. Created superuser roles for its members instead of admin roles.", c.Spec.TeamID)
return nil
}
err := c.initTeamMembers(c.Spec.TeamID, false)
if err != nil {
return fmt.Errorf("Cannot create a team %q of admins owning the PG cluster: %v", c.Spec.TeamID, err)
}
return nil
}
func (c *Cluster) initInfrastructureRoles() error {
// add infrastructure roles from the operator's definition
for username, newRole := range c.InfrastructureRoles {
+145 -2
View File
@@ -2,6 +2,9 @@ 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"
@@ -9,8 +12,6 @@ import (
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
"github.com/zalando-incubator/postgres-operator/pkg/util/teams"
"k8s.io/api/core/v1"
"reflect"
"testing"
)
const (
@@ -101,6 +102,7 @@ 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
@@ -108,7 +110,9 @@ func TestInitHumanUsers(t *testing.T) {
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"
@@ -146,6 +150,145 @@ func TestInitHumanUsers(t *testing.T) {
}
}
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": spec.PgUser{
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"
+7 -5
View File
@@ -210,12 +210,14 @@ func (c *Cluster) logVolumeChanges(old, new acidv1.Volume) {
c.logger.Debugf("diff\n%s\n", util.PrettyDiff(old, new))
}
func (c *Cluster) getTeamMembers() ([]string, error) {
if c.Spec.TeamID == "" {
func (c *Cluster) getTeamMembers(teamID string) ([]string, error) {
if teamID == "" {
return nil, fmt.Errorf("no teamId specified")
}
if !c.OpConfig.EnableTeamsAPI {
c.logger.Debug("team API is disabled, returning empty list of members")
c.logger.Debugf("team API is disabled, returning empty list of members for team %q", teamID)
return []string{}, nil
}
@@ -225,9 +227,9 @@ func (c *Cluster) getTeamMembers() ([]string, error) {
return []string{}, nil
}
teamInfo, err := c.teamsAPIClient.TeamInfo(c.Spec.TeamID, token)
teamInfo, err := c.teamsAPIClient.TeamInfo(teamID, token)
if err != nil {
c.logger.Warnf("could not get team info, returning empty list of team members: %v", err)
c.logger.Warnf("could not get team info for team %q, returning empty list of team members: %v", teamID, err)
return []string{}, nil
}
+1
View File
@@ -82,6 +82,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
result.EnableTeamSuperuser = fromCRD.TeamsAPI.EnableTeamSuperuser
result.TeamAdminRole = fromCRD.TeamsAPI.TeamAdminRole
result.PamRoleName = fromCRD.TeamsAPI.PamRoleName
result.PostgresSuperuserTeams = fromCRD.TeamsAPI.PostgresSuperuserTeams
result.APIPort = fromCRD.LoggingRESTAPI.APIPort
result.RingLogLines = fromCRD.LoggingRESTAPI.RingLogLines
+1 -1
View File
@@ -23,7 +23,7 @@ const fileWithNamespace = "/var/run/secrets/kubernetes.io/serviceaccount/namespa
// RoleOrigin contains the code of the origin of a role
type RoleOrigin int
// The rolesOrigin constant values should be sorted by the role priority.
// The rolesOrigin constant values must be sorted by the role priority for resolveNameConflict(...) to work.
const (
RoleOriginUnknown RoleOrigin = iota
RoleOriginManifest
+1
View File
@@ -103,6 +103,7 @@ type Config struct {
TeamAPIRoleConfiguration map[string]string `name:"team_api_role_configuration" default:"log_statement:all"`
PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"`
ProtectedRoles []string `name:"protected_role_names" default:"admin"`
PostgresSuperuserTeams []string `name:"postgres_superuser_teams" default:""`
}
// MustMarshal marshals the config or panics