Skip to content

Commit

Permalink
Avoid overwriting critical users. (#172)
Browse files Browse the repository at this point in the history
* Avoid overwriting critical users.

Disallow defining new users either in the cluster manifest, teams
API or infrastructure roles with the names mentioned in the new
protected_role_names parameter (list of comma-separated names)

Additionally, forbid defining a user with the name matching either
super_username or replication_username, so that we don't overwrite
system roles required for correct working of the operator itself.

Also, clear PostgreSQL roles on each sync first in order to avoid using
the old definitions that are no longer present in the current manifest,
infrastructure roles secret or the teams API.
  • Loading branch information
alexeyklyukin committed Dec 5, 2017
1 parent 022ce29 commit 1fb8cf7
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ The following steps will get you the docker image built and deployed.
of configuration parameters applied to the roles fetched from the API.
For instance, `team_api_role_configuration: log_statement:all,search_path:'public,"$user"'`.
By default is set to *"log_statement:all"*. See [PostgreSQL documentation on ALTER ROLE .. SET](https://www.postgresql.org/docs/current/static/sql-alterrole.html) for to learn about the available options.
* protected_role_names - a list of role names that should be forbidden as the manifest, infrastructure and teams API roles.
The default value is `admin`. Operator will also disallow superuser and replication roles to be redefined.


### Debugging the operator itself
Expand Down
26 changes: 26 additions & 0 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ func (c *Cluster) setStatus(status spec.PostgresStatus) {
// initUsers populates c.systemUsers and c.pgUsers maps.
func (c *Cluster) initUsers() error {
c.setProcessName("initializing users")

// clear our the previous state of the cluster users (in case we are running a sync).
c.systemUsers = map[string]spec.PgUser{}
c.pgUsers = map[string]spec.PgUser{}

c.initSystemUsers()

if err := c.initInfrastructureRoles(); err != nil {
Expand Down Expand Up @@ -615,6 +620,9 @@ func (c *Cluster) initRobotUsers() error {
return fmt.Errorf("invalid username: %q", username)
}

if c.shouldAvoidProtectedOrSystemRole(username, "manifest robot role") {
continue
}
flags, err := normalizeUserFlags(userFlags)
if err != nil {
return fmt.Errorf("invalid flags for user %q: %v", username, err)
Expand Down Expand Up @@ -648,6 +656,9 @@ func (c *Cluster) initHumanUsers() error {
flags := []string{constants.RoleFlagLogin}
memberOf := []string{c.OpConfig.PamRoleName}

if c.shouldAvoidProtectedOrSystemRole(username, "API role") {
continue
}
if c.OpConfig.EnableTeamSuperuser {
flags = append(flags, constants.RoleFlagSuperuser)
} else {
Expand Down Expand Up @@ -677,6 +688,9 @@ func (c *Cluster) initInfrastructureRoles() error {
if !isValidUsername(username) {
return fmt.Errorf("invalid username: '%v'", username)
}
if c.shouldAvoidProtectedOrSystemRole(username, "infrastructure role") {
continue
}
flags, err := normalizeUserFlags(data.Flags)
if err != nil {
return fmt.Errorf("invalid flags for user '%v': %v", username, err)
Expand All @@ -687,6 +701,18 @@ func (c *Cluster) initInfrastructureRoles() error {
return nil
}

func (c *Cluster) shouldAvoidProtectedOrSystemRole(username, purpose string) bool {
if c.isProtectedUsername(username) {
c.logger.Warnf("cannot initialize a new %s with the name of the protected user %q", purpose, username)
return true
}
if c.isSystemUsername(username) {
c.logger.Warnf("cannot initialize a new %s with the name of the system user %q", purpose, username)
return true
}
return false
}

// GetCurrentProcess provides name of the last process of the cluster
func (c *Cluster) GetCurrentProcess() spec.Process {
c.processMu.RLock()
Expand Down
17 changes: 16 additions & 1 deletion pkg/cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import (
"fmt"
"github.com/Sirupsen/logrus"
"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"
"reflect"
"testing"
)

var logger = logrus.New().WithField("test", "cluster")
var cl = New(Config{}, k8sutil.KubernetesClient{}, spec.Postgresql{}, logger)
var cl = New(Config{OpConfig: config.Config{ProtectedRoles: []string{"admin"},
Auth: config.Auth{SuperUsername: "postgres",
ReplicationUsername: "standby"}}},
k8sutil.KubernetesClient{}, spec.Postgresql{}, logger)

func TestInitRobotUsers(t *testing.T) {
testName := "TestInitRobotUsers"
Expand Down Expand Up @@ -47,6 +51,12 @@ func TestInitRobotUsers(t *testing.T) {
err: fmt.Errorf(`invalid flags for user "foobar": ` +
`conflicting user flags: "NOINHERIT" and "INHERIT"`),
},
{
manifestUsers: map[string]spec.UserFlags{"admin": {"superuser"}, "postgres": {"createdb"}},
infraRoles: map[string]spec.PgUser{},
result: map[string]spec.PgUser{},
err: nil,
},
}
for _, tt := range tests {
cl.Spec.Users = tt.manifestUsers
Expand Down Expand Up @@ -109,6 +119,11 @@ func TestInitHumanUsers(t *testing.T) {
result: map[string]spec.PgUser{"foo": {Name: "foo", MemberOf: []string{cl.OpConfig.PamRoleName}, Flags: []string{"LOGIN", "SUPERUSER"}},
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
},
{
existingRoles: map[string]spec.PgUser{},
teamRoles: []string{"admin", "standby"},
result: map[string]spec.PgUser{},
},
}

for _, tt := range tests {
Expand Down
13 changes: 13 additions & 0 deletions pkg/cluster/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ func isValidUsername(username string) bool {
return userRegexp.MatchString(username)
}

func (c *Cluster) isProtectedUsername(username string) bool {
for _, protected := range c.OpConfig.ProtectedRoles {
if username == protected {
return true
}
}
return false
}

func (c *Cluster) isSystemUsername(username string) bool {
return (username == c.OpConfig.SuperUsername || username == c.OpConfig.ReplicationUsername)
}

func isValidFlag(flag string) bool {
for _, validFlag := range []string{constants.RoleFlagSuperuser, constants.RoleFlagLogin, constants.RoleFlagCreateDB,
constants.RoleFlagInherit, constants.RoleFlagReplication, constants.RoleFlagByPassRLS} {
Expand Down
1 change: 1 addition & 0 deletions pkg/util/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type Config struct {
ClusterHistoryEntries int `name:"cluster_history_entries" default:"1000"`
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"`
}

// MustMarshal marshals the config or panics
Expand Down

0 comments on commit 1fb8cf7

Please sign in to comment.