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/sirupsen/logrus v1.6.0
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
golang.org/x/mod v0.3.0 // indirect
|
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
|
golang.org/x/tools v0.0.0-20200615222825-6aa8f57aacd9 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
k8s.io/api v0.18.3
|
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
|
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{
|
cluster := &Cluster{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
|
|
@ -135,7 +139,7 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
|
||||||
Secrets: make(map[types.UID]*v1.Secret),
|
Secrets: make(map[types.UID]*v1.Secret),
|
||||||
Services: make(map[PostgresRole]*v1.Service),
|
Services: make(map[PostgresRole]*v1.Service),
|
||||||
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
|
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
|
||||||
userSyncStrategy: users.DefaultUserSyncStrategy{},
|
userSyncStrategy: users.DefaultUserSyncStrategy{password_encryption},
|
||||||
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
|
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
|
||||||
podEventsQueue: podEventsQueue,
|
podEventsQueue: podEventsQueue,
|
||||||
KubeClient: kubeClient,
|
KubeClient: kubeClient,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const (
|
||||||
// an existing roles of another role membership, nor it removes the already assigned flag
|
// 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.
|
// (except for the NOLOGIN). TODO: process other NOflags, i.e. NOSUPERUSER correctly.
|
||||||
type DefaultUserSyncStrategy struct {
|
type DefaultUserSyncStrategy struct {
|
||||||
|
PasswordEncryption string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProduceSyncRequests figures out the types of changes that need to happen with the given users.
|
// 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 {
|
} else {
|
||||||
r := spec.PgSyncUserRequest{}
|
r := spec.PgSyncUserRequest{}
|
||||||
newMD5Password := util.PGUserPassword(newUser)
|
newMD5Password := util.NewEncryptor(strategy.PasswordEncryption).PGUserPassword(newUser)
|
||||||
|
|
||||||
if dbUser.Password != newMD5Password {
|
if dbUser.Password != newMD5Password {
|
||||||
r.User.Password = newMD5Password
|
r.User.Password = newMD5Password
|
||||||
|
|
@ -140,7 +141,7 @@ func (strategy DefaultUserSyncStrategy) createPgUser(user spec.PgUser, db *sql.D
|
||||||
if user.Password == "" {
|
if user.Password == "" {
|
||||||
userPassword = "PASSWORD NULL"
|
userPassword = "PASSWORD NULL"
|
||||||
} else {
|
} 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)
|
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
|
var resultStmt []string
|
||||||
|
|
||||||
if user.Password != "" || len(user.Flags) > 0 {
|
if user.Password != "" || len(user.Flags) > 0 {
|
||||||
alterStmt := produceAlterStmt(user)
|
alterStmt := produceAlterStmt(user, strategy.PasswordEncryption)
|
||||||
resultStmt = append(resultStmt, alterStmt)
|
resultStmt = append(resultStmt, alterStmt)
|
||||||
}
|
}
|
||||||
if len(user.MemberOf) > 0 {
|
if len(user.MemberOf) > 0 {
|
||||||
|
|
@ -174,14 +175,14 @@ func (strategy DefaultUserSyncStrategy) alterPgUser(user spec.PgUser, db *sql.DB
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func produceAlterStmt(user spec.PgUser) string {
|
func produceAlterStmt(user spec.PgUser, encryption string) string {
|
||||||
// ALTER ROLE ... LOGIN ENCRYPTED PASSWORD ..
|
// ALTER ROLE ... LOGIN ENCRYPTED PASSWORD ..
|
||||||
result := make([]string, 0)
|
result := make([]string, 0)
|
||||||
password := user.Password
|
password := user.Password
|
||||||
flags := user.Flags
|
flags := user.Flags
|
||||||
|
|
||||||
if password != "" {
|
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 {
|
if len(flags) != 0 {
|
||||||
result = append(result, strings.Join(flags, " "))
|
result = append(result, strings.Join(flags, " "))
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
"crypto/md5" // #nosec we need it to for PostgreSQL md5 passwords
|
"crypto/md5" // #nosec we need it to for PostgreSQL md5 passwords
|
||||||
cryptoRand "crypto/rand"
|
cryptoRand "crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -16,10 +19,14 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/zalando/postgres-operator/pkg/spec"
|
"github.com/zalando/postgres-operator/pkg/spec"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
md5prefix = "md5"
|
md5prefix = "md5"
|
||||||
|
scramsha256prefix = "SCRAM-SHA-256"
|
||||||
|
saltlength = 16
|
||||||
|
iterations = 4096
|
||||||
)
|
)
|
||||||
|
|
||||||
var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
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.
|
type Hasher func(user spec.PgUser) string
|
||||||
func PGUserPassword(user spec.PgUser) string {
|
type Random func(n int) string
|
||||||
if (len(user.Password) == md5.Size*2+len(md5prefix) && user.Password[:3] == md5prefix) || user.Password == "" {
|
|
||||||
|
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
|
// Avoid processing already encrypted or empty passwords
|
||||||
return user.Password
|
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.
|
s := md5.Sum([]byte(user.Password + user.Name)) // #nosec, using md5 since PostgreSQL uses it for hashing passwords.
|
||||||
return md5prefix + hex.EncodeToString(s[:])
|
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
|
// Diff returns diffs between 2 objects
|
||||||
func Diff(a, b interface{}) []string {
|
func Diff(a, b interface{}) []string {
|
||||||
return pretty.Diff(a, b)
|
return pretty.Diff(a, b)
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var pgUsers = []struct {
|
var pgUsers = []struct {
|
||||||
in spec.PgUser
|
in spec.PgUser
|
||||||
out string
|
outmd5 string
|
||||||
|
outscramsha256 string
|
||||||
}{{spec.PgUser{
|
}{{spec.PgUser{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Password: "password",
|
Password: "password",
|
||||||
Flags: []string{},
|
Flags: []string{},
|
||||||
MemberOf: []string{}},
|
MemberOf: []string{}},
|
||||||
"md587f77988ccb5aa917c93201ba314fcd4"},
|
"md587f77988ccb5aa917c93201ba314fcd4", "SCRAM-SHA-256$4096:c2FsdA==$lF4cRm/Jky763CN4HtxdHnjV4Q8AWTNlKvGmEFFU8IQ=:ub8OgRsftnk2ccDMOt7ffHXNcikRkQkq1lh4xaAqrSw="},
|
||||||
{spec.PgUser{
|
{spec.PgUser{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Password: "md592f413f3974bdf3799bb6fecb5f9f2c6",
|
Password: "md592f413f3974bdf3799bb6fecb5f9f2c6",
|
||||||
Flags: []string{},
|
Flags: []string{},
|
||||||
MemberOf: []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 {
|
var prettyDiffTest = []struct {
|
||||||
inA interface{}
|
inA interface{}
|
||||||
|
|
@ -107,9 +114,16 @@ func TestNameFromMeta(t *testing.T) {
|
||||||
|
|
||||||
func TestPGUserPassword(t *testing.T) {
|
func TestPGUserPassword(t *testing.T) {
|
||||||
for _, tt := range pgUsers {
|
for _, tt := range pgUsers {
|
||||||
pwd := PGUserPassword(tt.in)
|
e := NewEncryptor("md5")
|
||||||
if pwd != tt.out {
|
pwd := e.PGUserPassword(tt.in)
|
||||||
t.Errorf("PgUserPassword expected: %q, got: %q", tt.out, pwd)
|
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