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) | ||||||
|  |  | ||||||
|  | @ -13,19 +13,26 @@ 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