Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Use scram-sha-256 hash if postgresql parameter password_encryption se…
…t to do so.
  • Loading branch information
yanchenko-igor committed Jul 10, 2020
commit fcd35eb25658ab526a256571a860dbadc5fff061
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.5.1
golang.org/x/mod v0.3.0 // indirect
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
golang.org/x/tools v0.0.0-20200615222825-6aa8f57aacd9 // indirect
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.3
Expand Down
6 changes: 5 additions & 1 deletion pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres

return fmt.Sprintf("%s-%s", e.PodName, e.ResourceVersion), nil
})
password_encryption, ok := pgSpec.Spec.PostgresqlParam.Parameters["password_encryption"]
if !ok {
password_encryption = "md5"
}

cluster := &Cluster{
Config: cfg,
Expand All @@ -135,7 +139,7 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
Secrets: make(map[types.UID]*v1.Secret),
Services: make(map[PostgresRole]*v1.Service),
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
userSyncStrategy: users.DefaultUserSyncStrategy{},
userSyncStrategy: users.DefaultUserSyncStrategy{password_encryption},
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
podEventsQueue: podEventsQueue,
KubeClient: kubeClient,
Expand Down
11 changes: 6 additions & 5 deletions pkg/util/users/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
// an existing roles of another role membership, nor it removes the already assigned flag
// (except for the NOLOGIN). TODO: process other NOflags, i.e. NOSUPERUSER correctly.
type DefaultUserSyncStrategy struct {
PasswordEncryption string
}

// ProduceSyncRequests figures out the types of changes that need to happen with the given users.
Expand All @@ -45,7 +46,7 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
}
} else {
r := spec.PgSyncUserRequest{}
newMD5Password := util.PGUserPassword(newUser)
newMD5Password := util.PGUserPassword(newUser, strategy.PasswordEncryption)

if dbUser.Password != newMD5Password {
r.User.Password = newMD5Password
Expand Down Expand Up @@ -140,7 +141,7 @@ func (strategy DefaultUserSyncStrategy) createPgUser(user spec.PgUser, db *sql.D
if user.Password == "" {
userPassword = "PASSWORD NULL"
} else {
userPassword = fmt.Sprintf(passwordTemplate, util.PGUserPassword(user))
userPassword = fmt.Sprintf(passwordTemplate, util.PGUserPassword(user, strategy.PasswordEncryption))
}
query := fmt.Sprintf(createUserSQL, user.Name, strings.Join(userFlags, " "), userPassword)

Expand All @@ -155,7 +156,7 @@ func (strategy DefaultUserSyncStrategy) alterPgUser(user spec.PgUser, db *sql.DB
var resultStmt []string

if user.Password != "" || len(user.Flags) > 0 {
alterStmt := produceAlterStmt(user)
alterStmt := produceAlterStmt(user, strategy.PasswordEncryption)
resultStmt = append(resultStmt, alterStmt)
}
if len(user.MemberOf) > 0 {
Expand All @@ -174,14 +175,14 @@ func (strategy DefaultUserSyncStrategy) alterPgUser(user spec.PgUser, db *sql.DB
return nil
}

func produceAlterStmt(user spec.PgUser) string {
func produceAlterStmt(user spec.PgUser, encryption string) string {
// ALTER ROLE ... LOGIN ENCRYPTED PASSWORD ..
result := make([]string, 0)
password := user.Password
flags := user.Flags

if password != "" {
result = append(result, fmt.Sprintf(passwordTemplate, util.PGUserPassword(user)))
result = append(result, fmt.Sprintf(passwordTemplate, util.PGUserPassword(user, encryption)))
}
if len(flags) != 0 {
result = append(result, strings.Join(flags, " "))
Expand Down
33 changes: 30 additions & 3 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package util

import (
"crypto/hmac"
"crypto/md5" // #nosec we need it to for PostgreSQL md5 passwords
cryptoRand "crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"math/big"
Expand All @@ -16,10 +19,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/zalando/postgres-operator/pkg/spec"
"golang.org/x/crypto/pbkdf2"
)

const (
md5prefix = "md5"
md5prefix = "md5"
scramsha256prefix = "SCRAM-SHA-256"
saltlenght = 16
iterations = 4096
)

var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
Expand Down Expand Up @@ -62,11 +69,31 @@ func NameFromMeta(meta metav1.ObjectMeta) spec.NamespacedName {
}

// PGUserPassword is used to generate md5 password hash for a given user. It does nothing for already hashed passwords.
func PGUserPassword(user spec.PgUser) string {
if (len(user.Password) == md5.Size*2+len(md5prefix) && user.Password[:3] == md5prefix) || user.Password == "" {
func PGUserPassword(user spec.PgUser, encryption string) string {
if (len(user.Password) == md5.Size*2+len(md5prefix) && user.Password[:3] == md5prefix) ||
(len(user.Password) > len(scramsha256prefix) && user.Password[:len(scramsha256prefix)] == scramsha256prefix) || user.Password == "" {
// Avoid processing already encrypted or empty passwords
return user.Password
}
if encryption == "scram-sha-256" {
salt := []byte(RandomPassword(saltlenght))
key := pbkdf2.Key([]byte(user.Password), salt, iterations, 32, sha256.New)
mac := hmac.New(sha256.New, key)
mac.Write([]byte("Server Key"))
serverKey := mac.Sum(nil)
mac = hmac.New(sha256.New, key)
mac.Write([]byte("Client Key"))
clientKey := mac.Sum(nil)
storedKey := sha256.Sum256(clientKey)
pass := fmt.Sprintf("%s$%v:%s$%s:%s",
scramsha256prefix,
iterations,
base64.StdEncoding.EncodeToString(salt),
base64.StdEncoding.EncodeToString(storedKey[:]),
base64.StdEncoding.EncodeToString(serverKey),
)
return pass
}
s := md5.Sum([]byte(user.Password + user.Name)) // #nosec, using md5 since PostgreSQL uses it for hashing passwords.
return md5prefix + hex.EncodeToString(s[:])
}
Expand Down