Use scram-sha-256 hash if postgresql parameter password_encryption set to do so. (#995)
* Use scram-sha-256 hash if postgresql parameter password_encryption set to do so. * test fixed * Refactoring * code style
This commit is contained in:
parent
ec932f88d8
commit
002b47ec32
1
go.mod
1
go.mod
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -45,7 +46,7 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
|
|||
}
|
||||
} else {
|
||||
r := spec.PgSyncUserRequest{}
|
||||
newMD5Password := util.PGUserPassword(newUser)
|
||||
newMD5Password := util.NewEncryptor(strategy.PasswordEncryption).PGUserPassword(newUser)
|
||||
|
||||
if dbUser.Password != newMD5Password {
|
||||
r.User.Password = newMD5Password
|
||||
|
|
@ -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.NewEncryptor(strategy.PasswordEncryption).PGUserPassword(user))
|
||||
}
|
||||
query := fmt.Sprintf(createUserSQL, user.Name, strings.Join(userFlags, " "), userPassword)
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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.NewEncryptor(encryption).PGUserPassword(user)))
|
||||
}
|
||||
if len(flags) != 0 {
|
||||
result = append(result, strings.Join(flags, " "))
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
saltlength = 16
|
||||
iterations = 4096
|
||||
)
|
||||
|
||||
var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
|
@ -61,16 +68,62 @@ 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 == "" {
|
||||
type Hasher func(user spec.PgUser) string
|
||||
type Random func(n int) string
|
||||
|
||||
type Encryptor struct {
|
||||
encrypt Hasher
|
||||
random Random
|
||||
}
|
||||
|
||||
func NewEncryptor(encryption string) *Encryptor {
|
||||
e := Encryptor{random: RandomPassword}
|
||||
m := map[string]Hasher{
|
||||
"md5": e.PGUserPasswordMD5,
|
||||
"scram-sha-256": e.PGUserPasswordScramSHA256,
|
||||
}
|
||||
hasher, ok := m[encryption]
|
||||
if !ok {
|
||||
hasher = e.PGUserPasswordMD5
|
||||
}
|
||||
e.encrypt = hasher
|
||||
return &e
|
||||
}
|
||||
|
||||
func (e *Encryptor) PGUserPassword(user spec.PgUser) 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
|
||||
}
|
||||
return e.encrypt(user)
|
||||
}
|
||||
|
||||
func (e *Encryptor) PGUserPasswordMD5(user spec.PgUser) string {
|
||||
s := md5.Sum([]byte(user.Password + user.Name)) // #nosec, using md5 since PostgreSQL uses it for hashing passwords.
|
||||
return md5prefix + hex.EncodeToString(s[:])
|
||||
}
|
||||
|
||||
func (e *Encryptor) PGUserPasswordScramSHA256(user spec.PgUser) string {
|
||||
salt := []byte(e.random(saltlength))
|
||||
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
|
||||
}
|
||||
|
||||
// Diff returns diffs between 2 objects
|
||||
func Diff(a, b interface{}) []string {
|
||||
return pretty.Diff(a, b)
|
||||
|
|
|
|||
|
|
@ -12,20 +12,27 @@ import (
|
|||
)
|
||||
|
||||
var pgUsers = []struct {
|
||||
in spec.PgUser
|
||||
out string
|
||||
in spec.PgUser
|
||||
outmd5 string
|
||||
outscramsha256 string
|
||||
}{{spec.PgUser{
|
||||
Name: "test",
|
||||
Password: "password",
|
||||
Flags: []string{},
|
||||
MemberOf: []string{}},
|
||||
"md587f77988ccb5aa917c93201ba314fcd4"},
|
||||
"md587f77988ccb5aa917c93201ba314fcd4", "SCRAM-SHA-256$4096:c2FsdA==$lF4cRm/Jky763CN4HtxdHnjV4Q8AWTNlKvGmEFFU8IQ=:ub8OgRsftnk2ccDMOt7ffHXNcikRkQkq1lh4xaAqrSw="},
|
||||
{spec.PgUser{
|
||||
Name: "test",
|
||||
Password: "md592f413f3974bdf3799bb6fecb5f9f2c6",
|
||||
Flags: []string{},
|
||||
MemberOf: []string{}},
|
||||
"md592f413f3974bdf3799bb6fecb5f9f2c6"}}
|
||||
"md592f413f3974bdf3799bb6fecb5f9f2c6", "md592f413f3974bdf3799bb6fecb5f9f2c6"},
|
||||
{spec.PgUser{
|
||||
Name: "test",
|
||||
Password: "SCRAM-SHA-256$4096:S1ByZWhvYVV5VDlJNGZoVw==$ozLevu5k0pAQYRrSY+vZhetO6+/oB+qZvuutOdXR94U=:yADwhy0LGloXzh5RaVwLMFyUokwI17VkHVfKVuHu0Zs=",
|
||||
Flags: []string{},
|
||||
MemberOf: []string{}},
|
||||
"SCRAM-SHA-256$4096:S1ByZWhvYVV5VDlJNGZoVw==$ozLevu5k0pAQYRrSY+vZhetO6+/oB+qZvuutOdXR94U=:yADwhy0LGloXzh5RaVwLMFyUokwI17VkHVfKVuHu0Zs=", "SCRAM-SHA-256$4096:S1ByZWhvYVV5VDlJNGZoVw==$ozLevu5k0pAQYRrSY+vZhetO6+/oB+qZvuutOdXR94U=:yADwhy0LGloXzh5RaVwLMFyUokwI17VkHVfKVuHu0Zs="}}
|
||||
|
||||
var prettyDiffTest = []struct {
|
||||
inA interface{}
|
||||
|
|
@ -107,9 +114,16 @@ func TestNameFromMeta(t *testing.T) {
|
|||
|
||||
func TestPGUserPassword(t *testing.T) {
|
||||
for _, tt := range pgUsers {
|
||||
pwd := PGUserPassword(tt.in)
|
||||
if pwd != tt.out {
|
||||
t.Errorf("PgUserPassword expected: %q, got: %q", tt.out, pwd)
|
||||
e := NewEncryptor("md5")
|
||||
pwd := e.PGUserPassword(tt.in)
|
||||
if pwd != tt.outmd5 {
|
||||
t.Errorf("PgUserPassword expected: %q, got: %q", tt.outmd5, pwd)
|
||||
}
|
||||
e = NewEncryptor("scram-sha-256")
|
||||
e.random = func(n int) string { return "salt" }
|
||||
pwd = e.PGUserPassword(tt.in)
|
||||
if pwd != tt.outscramsha256 {
|
||||
t.Errorf("PgUserPassword expected: %q, got: %q", tt.outscramsha256, pwd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue