diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 56e5a3f7a..083bd08ef 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -26,6 +26,7 @@ import ( "github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" "github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" "github.bus.zalan.do/acid/postgres-operator/pkg/util/teams" + "github.bus.zalan.do/acid/postgres-operator/pkg/util/users" ) var ( @@ -58,6 +59,7 @@ type Cluster struct { Config logger *logrus.Entry pgUsers map[string]spec.PgUser + systemUsers map[string]spec.PgUser podEvents chan spec.PodEvent podSubscribers map[spec.NamespacedName]chan spec.PodEvent podSubscribersMu sync.RWMutex @@ -65,6 +67,7 @@ type Cluster struct { mu sync.Mutex masterLess bool podDispatcherRunning bool + userSyncStrategy spec.UserSyncer deleteOptions *v1.DeleteOptions } @@ -78,11 +81,13 @@ func New(cfg Config, pgSpec spec.Postgresql, logger *logrus.Entry) *Cluster { Postgresql: pgSpec, logger: lg, pgUsers: make(map[string]spec.PgUser), + systemUsers: make(map[string]spec.PgUser), podEvents: make(chan spec.PodEvent), podSubscribers: make(map[spec.NamespacedName]chan spec.PodEvent), kubeResources: kubeResources, masterLess: false, podDispatcherRunning: false, + userSyncStrategy: users.DefaultUserSyncStrategy{}, deleteOptions: &v1.DeleteOptions{OrphanDependents: &orphanDependents}, } @@ -426,12 +431,15 @@ func (c *Cluster) ReceivePodEvent(event spec.PodEvent) { } func (c *Cluster) initSystemUsers() { - c.pgUsers[c.OpConfig.SuperUsername] = spec.PgUser{ + // We don't actually use that to create users, delegating this + // task to Patroni. Those definitions are only used to create + // secrets, therefore, setting flags like SUPERUSER or REPLICATION + // is not necessary here + c.systemUsers[constants.SuperuserKeyName] = spec.PgUser{ Name: c.OpConfig.SuperUsername, Password: util.RandomPassword(constants.PasswordLength), } - - c.pgUsers[c.OpConfig.ReplicationUsername] = spec.PgUser{ + c.systemUsers[constants.ReplicationUserKeyName] = spec.PgUser{ Name: c.OpConfig.ReplicationUsername, Password: util.RandomPassword(constants.PasswordLength), } @@ -464,7 +472,9 @@ func (c *Cluster) initHumanUsers() error { return fmt.Errorf("Can't get list of team members: %s", err) } else { for _, username := range teamMembers { - c.pgUsers[username] = spec.PgUser{Name: username} + flags := []string{constants.RoleFlagLogin, constants.RoleFlagSuperuser} + memberOf := []string{c.OpConfig.PamRoleName} + c.pgUsers[username] = spec.PgUser{Name: username, Flags: flags, MemberOf: memberOf} } } @@ -477,6 +487,11 @@ func (c *Cluster) initInfrastructureRoles() error { if !isValidUsername(username) { return fmt.Errorf("Invalid username: '%s'", username) } + if flags, err := normalizeUserFlags(data.Flags); err != nil { + return fmt.Errorf("Invalid flags for user '%s': %s", username, err) + } else { + data.Flags = flags + } c.pgUsers[username] = data } return nil diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 43ddbe61d..14d0e1721 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -228,32 +228,48 @@ func persistentVolumeClaimTemplate(volumeSize, volumeStorageClass string) *v1.Pe return volumeClaim } -func (c *Cluster) genUserSecrets() (secrets map[string]*v1.Secret, err error) { +func (c *Cluster) genUserSecrets() (secrets map[string]*v1.Secret) { secrets = make(map[string]*v1.Secret, len(c.pgUsers)) namespace := c.Metadata.Namespace for username, pgUser := range c.pgUsers { //Skip users with no password i.e. human users (they'll be authenticated using pam) - if pgUser.Password == "" { - continue + secret := c.genSingleUserSecret(namespace, pgUser) + if secret != nil { + secrets[username] = secret } - secret := v1.Secret{ - ObjectMeta: v1.ObjectMeta{ - Name: c.credentialSecretName(username), - Namespace: namespace, - Labels: c.labelsSet(), - }, - Type: v1.SecretTypeOpaque, - Data: map[string][]byte{ - "username": []byte(pgUser.Name), - "password": []byte(pgUser.Password), - }, + } + /* special case for the system user */ + for _, systemUser := range c.systemUsers { + secret := c.genSingleUserSecret(namespace, systemUser) + if secret != nil { + secrets[systemUser.Name] = secret } - secrets[username] = &secret } return } +func (c *Cluster) genSingleUserSecret(namespace string, pgUser spec.PgUser) *v1.Secret { + //Skip users with no password i.e. human users (they'll be authenticated using pam) + if pgUser.Password == "" { + return nil + } + username := pgUser.Name + secret := v1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: c.credentialSecretName(username), + Namespace: namespace, + Labels: c.labelsSet(), + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + "username": []byte(pgUser.Name), + "password": []byte(pgUser.Password), + }, + } + return &secret +} + func (c *Cluster) genService(allowedSourceRanges []string) *v1.Service { service := &v1.Service{ ObjectMeta: v1.ObjectMeta{ diff --git a/pkg/cluster/pg.go b/pkg/cluster/pg.go index 090479ad2..eb8882a5d 100644 --- a/pkg/cluster/pg.go +++ b/pkg/cluster/pg.go @@ -8,18 +8,28 @@ import ( _ "github.com/lib/pq" "github.bus.zalan.do/acid/postgres-operator/pkg/spec" - "github.bus.zalan.do/acid/postgres-operator/pkg/util" + "github.com/lib/pq" + "github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" ) -var createUserSQL = `SET LOCAL synchronous_commit = 'local'; CREATE ROLE "%s" %s %s;` +var getUserSQL = `SELECT a.rolname, COALESCE(a.rolpassword, ''), a.rolsuper, a.rolinherit, + a.rolcreaterole, a.rolcreatedb, a.rolcanlogin, + ARRAY(SELECT b.rolname + FROM pg_catalog.pg_auth_members m + JOIN pg_catalog.pg_authid b ON (m.roleid = b.oid) + WHERE m.member = a.oid) as memberof + FROM pg_catalog.pg_authid a + WHERE a.rolname = ANY($1) + ORDER BY 1;` func (c *Cluster) pgConnectionString() string { hostname := fmt.Sprintf("%s.%s.svc.cluster.local", c.Metadata.Name, c.Metadata.Namespace) - password := c.pgUsers[c.OpConfig.SuperUsername].Password + username := c.systemUsers[constants.SuperuserKeyName].Name + password := c.systemUsers[constants.SuperuserKeyName].Password return fmt.Sprintf("host='%s' dbname=postgres sslmode=require user='%s' password='%s'", hostname, - c.OpConfig.SuperUsername, + username, strings.Replace(password, "$", "\\$", -1)) } @@ -33,6 +43,7 @@ func (c *Cluster) initDbConn() error { } err = conn.Ping() if err != nil { + conn.Close() return err } @@ -43,42 +54,48 @@ func (c *Cluster) initDbConn() error { return nil } -func (c *Cluster) createPgUser(user spec.PgUser) (isHuman bool, err error) { - var flags []string = user.Flags - - if user.Password == "" { - isHuman = true - flags = append(flags, "SUPERUSER") - flags = append(flags, fmt.Sprintf("IN ROLE \"%s\"", c.OpConfig.PamRoleName)) - } else { - isHuman = false +func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUserMap, err error) { + var rows *sql.Rows + users = make(spec.PgUserMap) + if rows, err = c.pgDb.Query(getUserSQL, pq.Array(userNames)); err != nil { + return nil, fmt.Errorf("Error when querying users: %s", err) } - - addLoginFlag := true - for _, v := range flags { - if v == "NOLOGIN" { - addLoginFlag = false - break + defer rows.Close() + for rows.Next() { + var ( + rolname, rolpassword string + rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool + memberof []string + ) + err := rows.Scan(&rolname, &rolpassword, &rolsuper, &rolinherit, + &rolcreaterole, &rolcreatedb, &rolcanlogin, pq.Array(&memberof)) + if err != nil { + return nil, fmt.Errorf("Error when processing user rows: %s", err) } - } - if addLoginFlag { - flags = append(flags, "LOGIN") - } - if !isHuman && user.MemberOf != "" { - flags = append(flags, fmt.Sprintf("IN ROLE \"%s\"", user.MemberOf)) - } - userFlags := strings.Join(flags, " ") - userPassword := fmt.Sprintf("ENCRYPTED PASSWORD '%s'", util.PGUserPassword(user)) - if user.Password == "" { - userPassword = "PASSWORD NULL" - } - query := fmt.Sprintf(createUserSQL, user.Name, userFlags, userPassword) - - _, err = c.pgDb.Query(query) // TODO: Try several times - if err != nil { - err = fmt.Errorf("DB error: %s", err) - return + flags := makeUserFlags(rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin) + // XXX: the code assumes the password we get from pg_authid is always MD5 + users[rolname] = spec.PgUser{Name: rolname, Password: rolpassword, Flags: flags, MemberOf: memberof} } - return + return users, nil +} + +func makeUserFlags(rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin bool) (result []string) { + if rolsuper { + result = append(result, constants.RoleFlagSuperuser) + } + if rolinherit { + result = append(result, constants.RoleFlagInherit) + } + if rolcreaterole { + result = append(result, constants.RoleFlagCreateRole) + } + if rolcreatedb { + result = append(result, constants.RoleFlagCreateDB) + } + if rolcanlogin { + result = append(result, constants.RoleFlagLogin) + } + + return result } diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index b6eeef252..8a5cc1991 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -6,9 +6,11 @@ import ( "k8s.io/client-go/pkg/api" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/apis/apps/v1beta1" - + "github.bus.zalan.do/acid/postgres-operator/pkg/util" "github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil" + "github.bus.zalan.do/acid/postgres-operator/pkg/spec" + "github.bus.zalan.do/acid/postgres-operator/pkg/util/constants" ) func (c *Cluster) loadResources() error { @@ -183,7 +185,7 @@ func (c *Cluster) createService() (*v1.Service, error) { return service, nil } -func (c *Cluster) updateService(newService *v1.Service) error { +func (c *Cluster) updateService(newService *v1.Service) error { if c.Service == nil { return fmt.Errorf("There is no Service in the cluster") } @@ -262,23 +264,29 @@ func (c *Cluster) deleteEndpoint() error { } func (c *Cluster) applySecrets() error { - secrets, err := c.genUserSecrets() - - if err != nil { - return fmt.Errorf("Can't get user Secrets") - } + secrets := c.genUserSecrets() for secretUsername, secretSpec := range secrets { secret, err := c.KubeClient.Secrets(secretSpec.Namespace).Create(secretSpec) if k8sutil.ResourceAlreadyExists(err) { + var userMap map[string]spec.PgUser curSecret, err := c.KubeClient.Secrets(secretSpec.Namespace).Get(secretSpec.Name) if err != nil { return fmt.Errorf("Can't get current Secret: %s", err) } c.logger.Debugf("Secret '%s' already exists, fetching it's password", util.NameFromMeta(curSecret.ObjectMeta)) - pwdUser := c.pgUsers[secretUsername] + if secretUsername == c.systemUsers[constants.SuperuserKeyName].Name { + secretUsername = constants.SuperuserKeyName + userMap = c.systemUsers + } else if secretUsername == c.systemUsers[constants.ReplicationUserKeyName].Name { + secretUsername = constants.ReplicationUserKeyName + userMap = c.systemUsers + } else { + userMap = c.pgUsers + } + pwdUser := userMap[secretUsername] pwdUser.Password = string(curSecret.Data["password"]) - c.pgUsers[secretUsername] = pwdUser + userMap[secretUsername] = pwdUser continue } else { @@ -305,23 +313,12 @@ func (c *Cluster) deleteSecret(secret *v1.Secret) error { return err } -func (c *Cluster) createUsers() error { +func (c *Cluster) createUsers() (err error) { // TODO: figure out what to do with duplicate names (humans and robots) among pgUsers - for username, user := range c.pgUsers { - if username == c.OpConfig.SuperUsername || username == c.OpConfig.ReplicationUsername { - continue - } - - isHuman, err := c.createPgUser(user) - var userType string - if isHuman { - userType = "human" - } else { - userType = "robot" - } - if err != nil { - c.logger.Warnf("Can't create %s user '%s': %s", userType, username, err) - } + reqs := c.userSyncStrategy.ProduceSyncRequests(nil, c.pgUsers) + err = c.userSyncStrategy.ExecuteSyncRequests(reqs, c.pgDb) + if err != nil { + return err } return nil diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 55b098ea9..69444444e 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -36,6 +36,14 @@ func (c *Cluster) SyncCluster(stopCh <-chan struct{}) { if err := c.syncStatefulSet(); err != nil { c.logger.Errorf("Can't sync StatefulSets: %s", err) } + if err := c.initDbConn(); err != nil { + c.logger.Errorf("Can't init db connection: %s", err) + } else { + c.logger.Debugf("Syncing Roles") + if err := c.SyncRoles(); err != nil { + c.logger.Errorf("Can't sync Roles: %s", err) + } + } } func (c *Cluster) syncSecrets() error { @@ -150,3 +158,23 @@ func (c *Cluster) syncStatefulSet() error { return nil } + +func (c *Cluster) SyncRoles() error { + var userNames []string + + if err := c.initUsers(); err != nil { + return err + } + for _, u := range c.pgUsers { + userNames = append(userNames, u.Name) + } + dbUsers, err := c.readPgUsersFromDatabase(userNames) + if err != nil { + return fmt.Errorf("Error getting users from the database: %s", err) + } + pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, c.pgUsers) + if err := c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil { + return fmt.Errorf("Error executing sync statements: %s", err) + } + return nil +} diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index 56596cc8b..d3190a56d 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -24,6 +24,7 @@ func isValidUsername(username string) bool { func normalizeUserFlags(userFlags []string) (flags []string, err error) { uniqueFlags := make(map[string]bool) + addLogin := true for _, flag := range userFlags { if !alphaNumericRegexp.MatchString(flag) { @@ -36,11 +37,25 @@ func normalizeUserFlags(userFlags []string) (flags []string, err error) { } } } + if uniqueFlags[constants.RoleFlagLogin] && uniqueFlags[constants.RoleFlagNoLogin] { + return nil, fmt.Errorf("Conflicting or redundant flags: LOGIN and NOLOGIN") + } flags = []string{} for k := range uniqueFlags { + if k == constants.RoleFlagNoLogin || k == constants.RoleFlagLogin { + addLogin = false + if k == constants.RoleFlagNoLogin { + // we don't add NOLOGIN to the list of flags to be consistent with what we get + // from the readPgUsersFromDatabase in SyncUsers + continue + } + } flags = append(flags, k) } + if addLogin { + flags = append(flags, constants.RoleFlagLogin) + } return } diff --git a/pkg/controller/postgresql.go b/pkg/controller/postgresql.go index 23956e6d3..e9be4c2be 100644 --- a/pkg/controller/postgresql.go +++ b/pkg/controller/postgresql.go @@ -159,7 +159,7 @@ func (c *Controller) processClusterEventsQueue(idx int) { func (c *Controller) queueClusterEvent(old, new *spec.Postgresql, eventType spec.EventType) { var ( - uid types.UID + uid types.UID clusterName spec.NamespacedName ) diff --git a/pkg/controller/util.go b/pkg/controller/util.go index 123e230cd..719d85e59 100644 --- a/pkg/controller/util.go +++ b/pkg/controller/util.go @@ -122,7 +122,7 @@ Users: case "password": t.Password = s case "inrole": - t.MemberOf = s + t.MemberOf = append(t.MemberOf, s) default: c.logger.Warnf("Unknown key %s", p) } diff --git a/pkg/spec/types.go b/pkg/spec/types.go index 3fbf7623b..e8a687f5f 100644 --- a/pkg/spec/types.go +++ b/pkg/spec/types.go @@ -1,6 +1,8 @@ package spec import ( + "database/sql" + "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/types" ) @@ -24,6 +26,13 @@ type ClusterEvent struct { WorkerID uint32 } +type SyncUserOperation int + +const ( + PGSyncUserAdd = iota + PGsyncUserAlter +) + type PodEvent struct { ClusterName NamespacedName PodName NamespacedName @@ -36,7 +45,19 @@ type PgUser struct { Name string Password string Flags []string - MemberOf string + MemberOf []string +} + +type PgUserMap map[string]PgUser + +type PgSyncUserRequest struct { + Kind SyncUserOperation + User PgUser +} + +type UserSyncer interface { + ProduceSyncRequests(dbUsers PgUserMap, newUsers PgUserMap) (req []PgSyncUserRequest) + ExecuteSyncRequests(req []PgSyncUserRequest, db *sql.DB) error } func (p NamespacedName) String() string { diff --git a/pkg/util/constants/constants.go b/pkg/util/constants/constants.go index 2edde34d3..c4bc30ac3 100644 --- a/pkg/util/constants/constants.go +++ b/pkg/util/constants/constants.go @@ -16,4 +16,12 @@ const ( ResourceName = TPRName + "s" PodRoleMaster = "master" PodRoleReplica = "replica" + SuperuserKeyName = "superuser" + ReplicationUserKeyName = "replication" + RoleFlagSuperuser = "SUPERUSER" + RoleFlagInherit = "INHERIT" + RoleFlagLogin = "LOGIN" + RoleFlagNoLogin = "NOLOGIN" + RoleFlagCreateRole = "CREATEROLE" + RoleFlagCreateDB = "CREATEDB" ) diff --git a/pkg/util/users/users.go b/pkg/util/users/users.go new file mode 100644 index 000000000..bd7a54e8f --- /dev/null +++ b/pkg/util/users/users.go @@ -0,0 +1,152 @@ +package users + +import ( + "database/sql" + "fmt" + "strings" + + "github.bus.zalan.do/acid/postgres-operator/pkg/spec" + "github.bus.zalan.do/acid/postgres-operator/pkg/util" +) + +const ( + createUserSQL = `SET LOCAL synchronous_commit = 'local'; CREATE ROLE "%s" %s %s;` + alterUserSQL = `ALTER ROLE "%s" %s` + grantToUserSQL = `GRANT %s TO "%s"` + doBlockStmt = `SET LOCAL synchronous_commit = 'local'; DO $$ BEGIN %s; END;$$;` + passwordTemplate = "ENCRYPTED PASSWORD '%s'" + inRoleTemplate = `IN ROLE %s` +) + +type DefaultUserSyncStrategy struct { +} + +func (s DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserMap, + newUsers spec.PgUserMap) (reqs []spec.PgSyncUserRequest) { + + // No existing roles are deleted or stripped of role memebership/flags + for name, newUser := range newUsers { + dbUser, exists := dbUsers[name] + if !exists { + reqs = append(reqs, spec.PgSyncUserRequest{spec.PGSyncUserAdd, newUser}) + } else { + r := spec.PgSyncUserRequest{} + newMD5Password := util.PGUserPassword(newUser) + + if dbUser.Password != newMD5Password { + r.User.Password = newMD5Password + r.Kind = spec.PGsyncUserAlter + } + if addNewRoles, equal := util.SubstractStringSlices(newUser.MemberOf, dbUser.MemberOf); !equal { + r.User.MemberOf = addNewRoles + r.Kind = spec.PGsyncUserAlter + } + if addNewFlags, equal := util.SubstractStringSlices(newUser.Flags, dbUser.Flags); !equal { + r.User.Flags = addNewFlags + r.Kind = spec.PGsyncUserAlter + } + if r.Kind == spec.PGsyncUserAlter { + r.User.Name = newUser.Name + reqs = append(reqs, r) + } + } + } + + return +} + +func (s DefaultUserSyncStrategy) ExecuteSyncRequests(reqs []spec.PgSyncUserRequest, db *sql.DB) error { + for _, r := range reqs { + switch r.Kind { + case spec.PGSyncUserAdd: + if err := s.createPgUser(r.User, db); err != nil { + return fmt.Errorf("Can't create user '%s': %s", r.User.Name, err) + } + case spec.PGsyncUserAlter: + if err := s.alterPgUser(r.User, db); err != nil { + return fmt.Errorf("Can't alter user '%s': %s", r.User.Name, err) + } + default: + return fmt.Errorf("Unrecognized operation: %s", r.Kind) + } + + } + return nil +} + +func (s DefaultUserSyncStrategy) createPgUser(user spec.PgUser, db *sql.DB) (err error) { + var userFlags []string + var userPassword string + + if len(user.Flags) > 0 { + userFlags = append(userFlags, user.Flags...) + } + if len(user.MemberOf) > 0 { + userFlags = append(userFlags, fmt.Sprintf(inRoleTemplate, quoteMemberList(user))) + } + + if user.Password == "" { + userPassword = "PASSWORD NULL" + } else { + userPassword = fmt.Sprintf(passwordTemplate, util.PGUserPassword(user)) + } + query := fmt.Sprintf(createUserSQL, user.Name, strings.Join(userFlags, " "), userPassword) + + _, err = db.Query(query) // TODO: Try several times + if err != nil { + err = fmt.Errorf("DB error: %s, query: %s", err, query) + return + } + + return +} + +func (s DefaultUserSyncStrategy) alterPgUser(user spec.PgUser, db *sql.DB) (err error) { + var resultStmt []string + + if user.Password != "" || len(user.Flags) > 0 { + alterStmt := produceAlterStmt(user) + resultStmt = append(resultStmt, alterStmt) + } + if len(user.MemberOf) > 0 { + grantStmt := produceGrantStmt(user) + resultStmt = append(resultStmt, grantStmt) + } + query := fmt.Sprintf(doBlockStmt, strings.Join(resultStmt, ";")) + + _, err = db.Query(query) // TODO: Try several times + if err != nil { + err = fmt.Errorf("DB error: %s query %s", err, query) + return + } + + return +} + +func produceAlterStmt(user spec.PgUser) string { + // ALTER ROLE ... LOGIN ENCRYPTED PASSWORD .. + result := make([]string, 1) + password := user.Password + flags := user.Flags + + if password != "" { + result = append(result, fmt.Sprintf(passwordTemplate, util.PGUserPassword(user))) + } + if len(flags) != 0 { + result = append(result, strings.Join(flags, " ")) + } + return fmt.Sprintf(alterUserSQL, user.Name, strings.Join(result, " ")) +} + +func produceGrantStmt(user spec.PgUser) string { + // GRANT ROLE "foo", "bar" TO baz + return fmt.Sprintf(grantToUserSQL, quoteMemberList(user), user.Name) +} + +func quoteMemberList(user spec.PgUser) string { + var memberof []string + for _, member := range user.MemberOf { + memberof = append(memberof, fmt.Sprintf(`"%s"`, member)) + } + return strings.Join(memberof, ",") +} diff --git a/pkg/util/util.go b/pkg/util/util.go index aaab41ab4..f0e1b045b 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -14,6 +14,10 @@ import ( "github.bus.zalan.do/acid/postgres-operator/pkg/spec" ) +const ( + MD5Prefix = "md5" +) + var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") func init() { @@ -37,9 +41,12 @@ func NameFromMeta(meta v1.ObjectMeta) spec.NamespacedName { } func PGUserPassword(user spec.PgUser) string { + if (len(user.Password) == md5.Size && user.Password[:3] == MD5Prefix) || user.Password == "" { + // Avoid processing already encrypted or empty passwords + return user.Password + } s := md5.Sum([]byte(user.Password + user.Name)) - - return "md5" + hex.EncodeToString(s[:]) + return MD5Prefix + hex.EncodeToString(s[:]) } func Pretty(x interface{}) (f fmt.Formatter) { @@ -50,3 +57,18 @@ func PrettyDiff(a, b interface{}) (result string) { diff := pretty.Diff(a, b) return strings.Join(diff, "\n") } + +func SubstractStringSlices(a []string, b []string) (result []string, equal bool) { + // Find elements in a that are not in b and return them as a result slice + // Slices are assumed to contain unique elements only +OUTER: + for _, vala := range a { + for _, valb := range b { + if vala == valb { + continue OUTER + } + } + result = append(result, vala) + } + return result, len(result) == 0 +}